Conflicts:
	gradle/apiGenerator.gradle
	gradle/apiGeneratorKotlinServer.gradle
	gradle/commonStaticAnalysis.gradle
	xcode/build_phases/api_generator.sh
This commit is contained in:
Anadol 2023-04-20 22:30:45 +03:00
commit 98bb3953f1
94 changed files with 3267 additions and 2139 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# macOS
.DS_Store

View File

@ -1 +1,26 @@
# BuildScripts
## Настройки форматирования
Позволяют настроить одинаковое форматирования кода в Android Studio у всех, кто работает на проекте.
Настройки соответствуют
[Правилам оформления Kotlin кода](https://styleguide.docs.touchin.ru/Coding/KotlinCodestyle.html)
Есть два варианта использования: подключить к проекту или импортировать схему в Android Studio.
### Как подключить к проекту:
1. Скопировать директорию [`codeStyles`](/codeStyles) в директорию проекта `.idea`
2. Добавить в файл `.gitignore` строку `!.idea/codeStyles`
3. Перезапустить Android Studio, чтобы настройки применились
При таком варианте настройки будут применены у всех, кто работает на проекте.
И только для одного конкретного проекта.
### Как импортировать схему в Android Studio:
1. Скачать схему [`codeStyles/Project.xml`](/codeStyles/Project.xml)
2. В Android Studio перейти в `File` > `Settings` > `Editor` > `Code Style`
3. Нажать на шестеренку справа от выпадающего списка схем и выбрать `Import Scheme`
4. В открывшемся окне указать путь до сохраненной схемы и нажать `ОК`
5. В открывшемся окне ввести название новой схемы и нажать `ОК`

View File

@ -1,210 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<!--
Checkstyle configuration that checks the Google coding conventions from:
- Google Java Style
https://google-styleguide.googlecode.com/svn-history/r130/trunk/javaguide.html
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.sf.net (or in your downloaded distribution).
Most Checks are configurable, be sure to consult the documentation.
To completely disable a check, just comment it out or delete it from the file.
Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
-->
<module name = "Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="warning"/>
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<module name="TreeWalker">
<module name="MethodLength">
<property name="max" value="40"/>
</module>
<module name="FinalParameters">
<property name="tokens" value="METHOD_DEF, CTOR_DEF, LITERAL_CATCH, FOR_EACH_CLAUSE"/>
</module>
<module name="OuterTypeFilename"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
<property name="format" value="\\u00(08|09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message" value="Avoid using corresponding octal or Unicode escape."/>
</module>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="LineLength">
<property name="max" value="150"/>
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
<module name="AvoidStarImport"/>
<module name="OneTopLevelClass"/>
<module name="NoLineWrap"/>
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="NeedBraces"/>
<module name="LeftCurly">
<property name="maxLineLength" value="150"/>
</module>
<module name="RightCurly"/>
<module name="RightCurly">
<property name="option" value="alone"/>
<property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, STATIC_INIT, INSTANCE_INIT"/>
</module>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLoops" value="true"/>
<message key="ws.notFollowed"
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
<message key="ws.notPreceded"
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
</module>
<module name="OneStatementPerLine"/>
<module name="MultipleVariableDeclarations"/>
<module name="ArrayTypeStyle"/>
<module name="MissingSwitchDefault"/>
<module name="FallThrough"/>
<module name="UpperEll"/>
<module name="ModifierOrder"/>
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="SeparatorWrap">
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern"
value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="TypeName">
<message key="name.invalidPattern"
value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MemberName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalVariableName">
<property name="tokens" value="VARIABLE_DEF"/>
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<property name="allowOneCharVarInForLoop" value="true"/>
<message key="name.invalidPattern"
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ClassTypeParameterName">
<property name="format" value="^[T]([A-Z][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
<property name="format" value="^[T]([A-Z][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="NoFinalizer"/>
<module name="GenericWhitespace">
<message key="ws.followed"
value="GenericWhitespace ''{0}'' is followed by whitespace."/>
<message key="ws.preceded"
value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
<message key="ws.illegalFollow"
value="GenericWhitespace ''{0}'' should followed by whitespace."/>
<message key="ws.notPreceded"
value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
</module>
<module name="Indentation">
<property name="basicOffset" value="4"/>
<property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="4"/>
<property name="throwsIndent" value="4"/>
<property name="lineWrappingIndentation" value="4"/>
<property name="arrayInitIndent" value="4"/>
</module>
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="false"/>
<property name="allowedAbbreviationLength" value="1"/>
</module>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<module name="MethodParamPad"/>
<module name="OperatorWrap">
<property name="option" value="NL"/>
<property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR "/>
</module>
<module name="AnnotationLocation">
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
</module>
<module name="AnnotationLocation">
<property name="tokens" value="VARIABLE_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<module name="NonEmptyAtclauseDescription"/>
<module name="JavadocTagContinuationIndentation"/>
<module name="SummaryJavadocCheck">
<property name="forbiddenSummaryFragments" value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
</module>
<module name="JavadocParagraph"/>
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
<property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
</module>
<module name="JavadocMethod">
<property name="scope" value="nothing"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingThrowsTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="minLineCount" value="2"/>
<property name="allowedAnnotations" value="Override, Test"/>
<property name="allowThrowsTagsForSubclasses" value="true"/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
<message key="name.invalidPattern"
value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="SingleLineJavadoc"/>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected"/>
</module>
<module name="SuppressWarningsHolder" />
</module>
<module name="SuppressWarningsFilter" />
</module>

View File

@ -1,339 +0,0 @@
<?xml version="1.0"?>
<!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="charset" value="UTF-8"/>
<property name="severity" value="warning"/>
<property name="fileExtensions" value="java, properties, xml"/>
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<module name="TreeWalker">
<!-- CUSTOM CHECKS START-->
<module name="NullAnnotationsCheck"/>
<module name="CustomDeclarationOrder">
<property name="customDeclarationOrder"
value="Field(static final)
### Field(static)
### Ctor(static)
### Method(static)
### Field(.*)
### Ctor(.*)
### Method(.*)
### InnerEnum(.*)
### InnerInterface(.*)
### InnerClass(.*)"/>
</module>
<!-- CUSTOM CHECKS END-->
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="false"/>
<property name="allowedAbbreviationLength" value="1"/>
</module>
<!-- <module name="AbstractClassName"/> -->
<module name="AnnotationLocation">
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
<property name="allowSamelineSingleParameterlessAnnotation" value="false"/>
</module>
<module name="AnnotationLocation">
<property name="tokens"
value="VARIABLE_DEF, PARAMETER_DEF, ANNOTATION_DEF, TYPECAST, LITERAL_THROWS, IMPLEMENTS_CLAUSE, TYPE_ARGUMENT, LITERAL_NEW, DOT, ANNOTATION_FIELD_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<!-- <module name="AnnotationUseStyle"/> -->
<!-- <module name="AnonInnerLength"/> -->
<!-- <module name="ArrayTrailingComma"/> -->
<module name="ArrayTypeStyle"/>
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
<property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
</module>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<!-- <module name="AvoidInlineConditionals"/> -->
<module name="AvoidNestedBlocks">
<property name="allowInSwitchCase" value="true"/>
</module>
<module name="AvoidStarImport"/>
<module name="AvoidStaticImport"/>
<!-- <module name="BooleanExpressionComplexity"/> -->
<module name="CatchParameterName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<!-- <module name="ClassDataAbstractionCoupling"/> -->
<!-- <module name="ClassFanOutComplexity"/> -->
<module name="ClassTypeParameterName">
<property name="format" value="^[T]([A-Z][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="CommentsIndentation"/>
<module name="ConstantName"/>
<!-- <module name="CovariantEquals"/> -->
<!-- <module name="CustomImportOrder"/> -->
<!-- <module name="CyclomaticComplexity"/> -->
<!-- <module name="DeclarationOrder"/> -->
<module name="DefaultComesLast"/>
<!-- <module name="DescendantToken"/> -->
<!-- <module name="DesignForExtension"/> -->
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens"
value="LITERAL_WHILE, LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_DO, LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, INSTANCE_INIT, STATIC_INIT, LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_CASE, LITERAL_DEFAULT"/>
</module>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected"/>
</module>
<!-- <module name="EmptyForInitializerPad"/> -->
<!-- <module name="EmptyForIteratorPad"/> -->
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="EmptyStatement"/>
<!-- <module name="EqualsAvoidNull"/> -->
<module name="EqualsHashCode"/>
<!-- <module name="ExecutableStatementCount"/> -->
<!-- <module name="ExplicitInitialization"/> -->
<module name="FallThrough"/>
<!-- <module name="FileLength"/> -->
<!-- <module name="FinalClass"/> -->
<module name="FinalLocalVariable"/>
<module name="FinalParameters">
<property name="tokens" value="METHOD_DEF, CTOR_DEF, LITERAL_CATCH, FOR_EACH_CLAUSE"/>
</module>
<module name="GenericWhitespace">
<message key="ws.followed"
value="GenericWhitespace ''{0}'' is followed by whitespace."/>
<message key="ws.preceded"
value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
<message key="ws.illegalFollow"
value="GenericWhitespace ''{0}'' should followed by whitespace."/>
<message key="ws.notPreceded"
value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
</module>
<!-- <module name="Header"/> -->
<!-- <module name="HiddenField"> -->
<!-- <module name="HideUtilityClassConstructor"/> -->
<!-- <module name="IllegalCatch"/> -->
<!-- <module name="IllegalImport"/> -->
<!-- <module name="IllegalInstantiation"/> -->
<!-- <module name="IllegalThrows"/> -->
<!-- <module name="IllegalToken"/> -->
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
<property name="format" value="\\u00(08|09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message" value="Avoid using corresponding octal or Unicode escape."/>
</module>
<!-- <module name="IllegalType"/> -->
<!-- <module name="ImportControl"/> -->
<!-- <module name="ImportOrder"/> -->
<module name="Indentation">
<property name="basicOffset" value="4"/>
<property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="4"/>
<property name="throwsIndent" value="4"/>
<property name="lineWrappingIndentation" value="4"/>
<property name="arrayInitIndent" value="8"/>
</module>
<module name="InnerAssignment"/>
<module name="InnerTypeLast"/>
<module name="InterfaceIsType"/>
<module name="InterfaceTypeParameterName">
<property name="format" value="^[T]([A-Z][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="JavadocMethod">
<property name="scope" value="nothing"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingThrowsTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="minLineCount" value="2"/>
<property name="allowedAnnotations" value="Override, Test"/>
<property name="allowThrowsTagsForSubclasses" value="true"/>
</module>
<!-- <module name="JavadocPackage"/> -->
<module name="JavadocParagraph"/>
<!-- <module name="JavadocStyle"/> -->
<!-- https://youtrack.jetbrains.com/issue/IDEA-121723 <module name="JavadocTagContinuationIndentation"/> -->
<!-- <module name="JavadocType"/> -->
<!-- <module name="JavadocVariable"/> -->
<!-- <module name="JavaNCSS"/> -->
<module name="LeftCurly">
<property name="maxLineLength" value="150"/>
</module>
<module name="LineLength">
<property name="max" value="150"/>
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
<module name="LocalFinalVariableName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Final variable/parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalVariableName">
<property name="allowOneCharVarInForLoop" value="true"/>
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<!-- <module name="MagicNumber"/> -->
<module name="MemberName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<!-- <module name="MethodCount"/> -->
<module name="MethodLength">
<property name="max" value="40"/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
<message key="name.invalidPattern"
value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodParamPad"/>
<module name="MethodTypeParameterName">
<property name="format" value="^[T]([A-Z][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<!-- <module name="MissingCtor"/> -->
<!-- <module name="MissingDeprecated"/> -->
<module name="MissingOverride"/>
<module name="MissingSwitchDefault"/>
<!-- <module name="ModifiedControlVariable"/> -->
<module name="ModifierOrder"/>
<!-- <module name="MultipleStringLiterals"/> -->
<module name="MultipleVariableDeclarations"/>
<module name="MutableException"/>
<module name="NeedBraces"/>
<!-- <module name="NestedForDepth"/> -->
<!-- <module name="NestedIfDepth"/> -->
<!-- <module name="NestedTryDepth"/> -->
<!-- <module name="NewlineAtEndOfFile"/> -->
<module name="NoClone"/>
<module name="NoFinalizer"/>
<module name="NoLineWrap"/>
<module name="NonEmptyAtclauseDescription"/>
<module name="NoWhitespaceAfter">
<property name="tokens"
value="ARRAY_INIT, INC, DEC, UNARY_MINUS, UNARY_PLUS, BNOT, LNOT, DOT, ARRAY_DECLARATOR, INDEX_OP"/>
</module>
<module name="NoWhitespaceBefore">
<property name="tokens" value="COMMA, SEMI, POST_INC, POST_DEC, GENERIC_END"/>
</module>
<!-- <module name="NPathComplexity"/> -->
<module name="OneStatementPerLine"/>
<module name="OneTopLevelClass"/>
<module name="OperatorWrap">
<property name="option" value="NL"/>
<property name="tokens"
value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR "/>
</module>
<module name="OuterTypeFilename"/>
<!-- <module name="OuterTypeNumber"/> -->
<module name="OverloadMethodsDeclarationOrder"/>
<!-- <module name="PackageAnnotation"/> -->
<!-- <module name="PackageDeclaration"/> -->
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern"
value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterAssignment"/>
<module name="ParameterName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<!-- <module name="ParameterNumber"/> -->
<!-- <module name="ParenPad"/> -->
<module name="RedundantImport"/>
<!-- <module name="RedundantModifier"/> -->
<!-- <module name="Regexp"/> -->
<!-- <module name="RegexpHeader"/> -->
<!-- <module name="RegexpMultiline"/> -->
<!-- <module name="RegexpOnFilename"/> -->
<!-- <module name="RegexpSingleline"/> -->
<!-- <module name="RegexpSinglelineJava"/> -->
<!-- <module name="ReturnCount"/> -->
<module name="RequireThis">
<property name="checkMethods" value="false"/>
</module>
<module name="RightCurly"/>
<module name="RightCurly">
<property name="option" value="alone"/>
<property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, STATIC_INIT, INSTANCE_INIT"/>
</module>
<module name="SeparatorWrap">
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<module name="SingleLineJavadoc"/>
<module name="SingleSpaceSeparator"/>
<module name="StaticVariableName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Static variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="StringLiteralEquality"/>
<module name="SummaryJavadoc">
<property name="forbiddenSummaryFragments" value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
</module>
<!-- <module name="SuperClone"/> -->
<!-- <module name="SuperFinalize"/> -->
<!-- <module name="SuppressWarnings"/> -->
<module name="SuppressWarningsHolder"/>
<!-- <module name="ThrowsCount"/> -->
<!-- <module name="TodoComment"/> -->
<!-- <module name="TrailingComment"/> -->
<!-- <module name="Translation"/> -->
<!-- <module name="TypecastParenPad"/> -->
<module name="TypeName">
<message key="name.invalidPattern"
value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<!-- <module name="UncommentedMain"/> -->
<!-- <module name="UniqueProperties"/> -->
<module name="UnnecessaryParentheses">
<property name="tokens"
value=" EXPR, IDENT, NUM_DOUBLE, NUM_FLOAT, NUM_INT, NUM_LONG, STRING_LITERAL, LITERAL_NULL, LITERAL_FALSE, LITERAL_TRUE, ASSIGN, BAND_ASSIGN, BOR_ASSIGN, BSR_ASSIGN, BXOR_ASSIGN, DIV_ASSIGN, MINUS_ASSIGN, MOD_ASSIGN, PLUS_ASSIGN, SL_ASSIGN, SR_ASSIGN, STAR_ASSIGN"/>
</module>
<module name="UnusedImports"/>
<module name="UpperEll"/>
<module name="VariableDeclarationUsageDistance"/>
<!-- <module name="VisibilityModifier"/> -->
<!-- <module name="WhitespaceAfter"/> -->
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLoops" value="true"/>
<message key="ws.notFollowed"
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
<message key="ws.notPreceded"
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
</module>
<!-- <module name="WriteTag"/> -->
</module>
<module name="SuppressWarningsFilter"/>
</module>

View File

@ -1,8 +0,0 @@
<application>
<component name="CodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value />
</option>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="TouchInstinct" />
</component>
</application>

View File

@ -1,5 +0,0 @@
<application>
<component name="CodeStyleSchemeSettings">
<option name="CURRENT_SCHEME_NAME" value="TouchInstinct" />
</component>
</application>

249
codeStyles/Project.xml Normal file
View File

@ -0,0 +1,249 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="RIGHT_MARGIN" value="120" />
<option name="SOFT_MARGINS" value="100" />
<AndroidXmlCodeStyleSettings>
<option name="LAYOUT_SETTINGS">
<value>
<option name="INSERT_LINE_BREAK_BEFORE_NAMESPACE_DECLARATION" value="true" />
</value>
</option>
<option name="MANIFEST_SETTINGS">
<value>
<option name="INSERT_LINE_BREAK_BEFORE_NAMESPACE_DECLARATION" value="true" />
</value>
</option>
<option name="OTHER_SETTINGS">
<value>
<option name="INSERT_LINE_BREAK_BEFORE_NAMESPACE_DECLARATION" value="true" />
</value>
</option>
</AndroidXmlCodeStyleSettings>
<JavaCodeStyleSettings>
<option name="GENERATE_FINAL_LOCALS" value="true" />
<option name="GENERATE_FINAL_PARAMETERS" value="true" />
<option name="DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION" value="true" />
<option name="ANNOTATION_PARAMETER_WRAP" value="1" />
<option name="ALIGN_MULTILINE_ANNOTATION_PARAMETERS" value="true" />
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="android" static="false" withSubpackages="true" />
<emptyLine />
<package name="com" static="false" withSubpackages="true" />
<emptyLine />
<package name="junit" static="false" withSubpackages="true" />
<emptyLine />
<package name="net" static="false" withSubpackages="true" />
<emptyLine />
<package name="org" static="false" withSubpackages="true" />
<emptyLine />
<package name="java" static="false" withSubpackages="true" />
<emptyLine />
<package name="javax" static="false" withSubpackages="true" />
<emptyLine />
<package name="" static="false" withSubpackages="true" />
<emptyLine />
<package name="" static="true" withSubpackages="true" />
<emptyLine />
</value>
</option>
<option name="JD_P_AT_EMPTY_LINES" value="false" />
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value />
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CONTINUATION_INDENT_IN_PARAMETER_LISTS" value="true" />
<option name="CONTINUATION_INDENT_IN_ARGUMENT_LISTS" value="true" />
<option name="CONTINUATION_INDENT_FOR_EXPRESSION_BODIES" value="true" />
<option name="CONTINUATION_INDENT_FOR_CHAINED_CALLS" value="true" />
<option name="CONTINUATION_INDENT_IN_ELVIS" value="true" />
<option name="ALLOW_TRAILING_COMMA" value="true" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair header="h" source="cpp" />
<pair header="h" source="c" />
</extensions>
</Objective-C-extensions>
<XML>
<option name="XML_KEEP_BLANK_LINES" value="1" />
</XML>
<codeStyleSettings language="JAVA">
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_WRAP" value="1" />
<option name="RESOURCE_LIST_WRAP" value="1" />
<option name="EXTENDS_LIST_WRAP" value="1" />
<option name="THROWS_LIST_WRAP" value="1" />
<option name="EXTENDS_KEYWORD_WRAP" value="1" />
<option name="THROWS_KEYWORD_WRAP" value="1" />
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
<option name="BINARY_OPERATION_WRAP" value="1" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="TERNARY_OPERATION_WRAP" value="1" />
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
<option name="FOR_STATEMENT_WRAP" value="1" />
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
<option name="ASSIGNMENT_WRAP" value="1" />
<option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<option name="WRAP_LONG_LINES" value="true" />
<option name="PARAMETER_ANNOTATION_WRAP" value="1" />
<option name="VARIABLE_ANNOTATION_WRAP" value="1" />
<option name="ENUM_CONSTANTS_WRAP" value="5" />
</codeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
<option name="ENUM_CONSTANTS_WRAP" value="5" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@ -1,254 +0,0 @@
<code_scheme name="TouchInstinct">
<option name="GENERATE_FINAL_LOCALS" value="true" />
<option name="GENERATE_FINAL_PARAMETERS" value="true" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="android" withSubpackages="true" static="false" />
<emptyLine />
<package name="com" withSubpackages="true" static="false" />
<emptyLine />
<package name="junit" withSubpackages="true" static="false" />
<emptyLine />
<package name="net" withSubpackages="true" static="false" />
<emptyLine />
<package name="org" withSubpackages="true" static="false" />
<emptyLine />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
</value>
</option>
<option name="RIGHT_MARGIN" value="150" />
<option name="JD_P_AT_EMPTY_LINES" value="false" />
<AndroidXmlCodeStyleSettings>
<option name="USE_CUSTOM_SETTINGS" value="true" />
</AndroidXmlCodeStyleSettings>
<JavaCodeStyleSettings>
<option name="DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION" value="true" />
<option name="ANNOTATION_PARAMETER_WRAP" value="1" />
<option name="ALIGN_MULTILINE_ANNOTATION_PARAMETERS" value="true" />
</JavaCodeStyleSettings>
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" />
<pair source="c" header="h" />
</extensions>
</Objective-C-extensions>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
<codeStyleSettings language="JAVA">
<option name="RIGHT_MARGIN" value="150" />
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_WRAP" value="1" />
<option name="RESOURCE_LIST_WRAP" value="1" />
<option name="EXTENDS_LIST_WRAP" value="1" />
<option name="THROWS_LIST_WRAP" value="1" />
<option name="EXTENDS_KEYWORD_WRAP" value="1" />
<option name="THROWS_KEYWORD_WRAP" value="1" />
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
<option name="BINARY_OPERATION_WRAP" value="1" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="TERNARY_OPERATION_WRAP" value="1" />
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
<option name="FOR_STATEMENT_WRAP" value="1" />
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
<option name="ASSIGNMENT_WRAP" value="1" />
<option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<option name="WRAP_LONG_LINES" value="true" />
<option name="PARAMETER_ANNOTATION_WRAP" value="1" />
<option name="VARIABLE_ANNOTATION_WRAP" value="1" />
<option name="ENUM_CONSTANTS_WRAP" value="2" />
</codeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>

View File

@ -1,31 +0,0 @@
#!/bin/sh
PROJECT_NAME=$1
SRC_FOLDER_NAME=${PROJECT_NAME}-src-$(date +%F)
SRC_DIR=./${SRC_FOLDER_NAME}
COMMAND_LINE_ARGUMENTS=$@
clone_platform() {
PROJECT_DIR=$1
PLATFORM=$2
git clone --recurse-submodules -j8 git@github.com:TouchInstinct/${PROJECT_DIR}-${PLATFORM}.git --branch master
}
mkdir -p ${SRC_DIR}
cd ${SRC_DIR}
for argument in ${COMMAND_LINE_ARGUMENTS}
do
if [ $argument != $PROJECT_NAME ]
then
platform=${argument} # all arguments after project name treated as platforms
clone_platform ${PROJECT_NAME} ${platform}
fi
done
find . -name ".git*" -print0 | xargs -0 rm -rf
zip -r ${SRC_FOLDER_NAME}.zip .
open .

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<!-- ignore all issues in resource generation -->
<Class name="~.*\.R\$.*"/>
</Match>
<Match>
<Class name="~.*\.Manifest\$.*"/>
</Match>
<Match>
<!-- ignore concrete check groups -->
<Bug code="DP"/>
</Match>
<Match>
<!-- ignore concrete checks -->
<Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE, NP_UNWRITTEN_FIELD, UWF_NULL_FIELD, UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR, PZLA_PREFER_ZERO_LENGTH_ARRAYS, EI_EXPOSE_REP, EI_EXPOSE_REP2, REC_CATCH_EXCEPTION, EQ_COMPARETO_USE_OBJECT_EQUALS, BC_UNCONFIRMED_CAST, EQ_DOESNT_OVERRIDE_EQUALS, BC_IMPOSSIBLE_CAST" />
</Match>
<Match>
<Bug code="UPM" />
<Class name="~.*\$\$Lambda\$.*"/>
</Match>
</FindBugsFilter>

View File

@ -1,42 +0,0 @@
repositories {
maven {
url "https://maven.dev.touchin.ru"
metadataSources {
artifact()
}
}
}
configurations {
apigenerator
}
dependencies {
apigenerator 'ru.touchin:api-generator:1.4.0-beta1'
}
android.applicationVariants.all { variant ->
final File generatedModelsDirectory = new File("${project.buildDir}/generated/source/models/${variant.dirName}")
def generateJsonModelsTask = tasks.create("apiGenerator${variant.name}") doLast {
javaexec {
main = "-jar"
workingDir = file("${rootDir}")
args = [
configurations.apigenerator.asPath,
"generate-client-code",
"--output-language",
"JAVA",
"--specification-path",
rootProject.extensions.findByName("pathToApiSchemes"),
"--output-path",
"${generatedModelsDirectory.path}",
"--package-name",
"${rootProject.extensions.findByName("applicationId") ?: applicationId}"
]
}
}
generateJsonModelsTask.description = 'Generates Java classes for JSON models'
variant.registerJavaGeneratingTask generateJsonModelsTask, generatedModelsDirectory
}

View File

@ -1,38 +0,0 @@
repositories {
maven {
url "https://maven.dev.touchin.ru"
metadataSources {
artifact()
}
}
}
configurations {
apigeneratorKotlinServer
}
dependencies {
apigeneratorKotlinServer 'ru.touchin:api-generator:1.4.0-beta1'
}
task generateApiModelsKotlinServer doLast {
javaexec {
main = "-jar"
workingDir = file("${rootDir}")
args = [
configurations.apigeneratorKotlinServer.asPath,
"generate-client-code",
"--output-language",
"KOTLIN_SERVER",
"--specification-path",
rootProject.extensions.findByName("pathToApiSchemes"),
"--output-path",
"${rootDir}/src/main/kotlin",
"--package-name",
rootProject.extensions.findByName("apiPackageName"),
"--recreate_output_dirs",
false
]
}
}

View File

@ -1,233 +0,0 @@
apply plugin: 'cpd'
apply plugin: 'io.gitlab.arturbosch.detekt'
def getCpdTask
def getLintTask
def getKotlinDetektTasks
def appendError
def appendCpdErrors
def appendKotlinErrors
def appendLintErrors
repositories {
maven { url "https://maven.dev.touchin.ru" }
}
configurations {
pngtastic
}
cpd {
skipLexicalErrors = true
}
import org.apache.tools.ant.taskdefs.condition.Os
ext.getIdeaFormatTask = { isAndroidProject, sources ->
def ideaPath = System.getenv("IDEA_HOME")
if (ideaPath == null) {
return tasks.create((isAndroidProject ? "android" : "server") + "donothing")
}
return tasks.create((isAndroidProject ? "android" : "server") + "IdeaFormat_$project.name", Exec) {
def inspectionPath
def params = ["-r", "-mask", "*.java,*.kt,*.xml"]
for (String source : sources) {
params.add(source)
}
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
inspectionPath = ['cmd', '/c', "\"${ideaPath}\\bin\\format.bat\" ${params.join(" ")}"]
} else {
inspectionPath = ["$ideaPath/bin/format.sh"]
}
commandLine inspectionPath
if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
args = params
}
}
}
ext.getStaticAnalysisTaskNames = { isAndroidProject, sources, buildVariant ->
def tasksNames = new ArrayList<String>()
try {
tasksNames.add(getCpdTask(isAndroidProject, sources))
tasksNames.addAll(getKotlinDetektTasks())
if (isAndroidProject) {
tasksNames.add(getLintTask(buildVariant))
}
} catch (Exception exception) {
println(exception.toString())
}
return tasksNames
}
ext.generateReport = { isAndroidProject ->
StringBuilder consoleReport = new StringBuilder()
consoleReport.append("STATIC ANALYSIS RESULTS:")
def count = 0
def previousCount = count
count = appendCpdErrors(count, new File("${project.buildDir}/reports/cpd.xml"))
if (count - previousCount > 0) {
consoleReport.append("\nCPD: FAILED (" + (count - previousCount) + " errors)")
} else {
consoleReport.append("\nCPD: PASSED")
}
previousCount = count
subprojects.forEach { subproject ->
def reportFile = new File("${rootProject.buildDir}/reports/kotlin-detekt-${subproject.name}.xml")
if (reportFile.exists()) {
count = appendKotlinErrors(count, reportFile).toInteger()
}
}
if (count - previousCount > 0) {
consoleReport.append("\nKotlin-detekt: FAILED (" + (count - previousCount) + " errors)")
} else {
consoleReport.append("\nKotlin-detekt: PASSED")
}
if (isAndroidProject) {
previousCount = count
count = appendLintErrors(count, new File("${rootProject.buildDir}/reports/lint_report.xml"))
if (count - previousCount > 0) {
consoleReport.append("\nLint: FAILED (" + (count - previousCount) + " errors)")
} else {
consoleReport.append("\nLint: PASSED")
}
}
if (count > 0) {
consoleReport.append("\nOverall: FAILED (" + count + " errors)")
throw new Exception(consoleReport.toString())
} else {
consoleReport.append("\nOverall: PASSED")
println(consoleReport.toString())
}
}
appendError = { number, analyzer, file, line, errorId, errorLink, description ->
println("$number. $analyzer : $description ($errorId)\n\tat $file: $line")
}
appendKotlinErrors = { count, checkstyleFile ->
def rootNode = new XmlParser().parse(checkstyleFile)
for (def fileNode : rootNode.children()) {
if (!fileNode.name().equals("file")) {
continue
}
for (def errorNode : fileNode.children()) {
if (!errorNode.name().equals("error")) {
continue
}
count++
appendError(count, "Detekt", fileNode.attribute("name"), errorNode.attribute("line"), errorNode.attribute("source"), "", errorNode.attribute("message"))
}
}
return count
}
appendCpdErrors = { count, cpdFile ->
def rootNode = new XmlParser().parse(cpdFile)
for (def duplicationNode : rootNode.children()) {
if (!duplicationNode.name().equals("duplication")) {
continue
}
count++
def duplicationIndex = 0
String duplicationPoints = ""
for (def filePointNode : duplicationNode.children()) {
if (filePointNode.name().equals("file")) {
def file = filePointNode.attribute("path")
def line = filePointNode.attribute("line")
duplicationPoints += "\n " + file + ":" + line
duplicationIndex++
}
}
println("$count CPD: code duplication $duplicationPoints")
}
return count
}
appendLintErrors = { count, lintFile ->
def rootNode = new XmlParser().parse(lintFile)
for (def issueNode : rootNode.children()) {
if (!issueNode.name().equals("issue")
|| !issueNode.attribute("severity").equals("Error")) {
continue
}
for (def locationNode : issueNode.children()) {
if (!locationNode.name().equals("location")) {
continue
}
count++
appendError(count, "Lint", locationNode.attribute("file"), locationNode.attribute("line"),
issueNode.attribute("id"), issueNode.attribute("explanation"), issueNode.attribute("message"))
}
}
return count
}
getCpdTask = { isAndroidProject, sources ->
def taskName = (isAndroidProject ? "android" : "server") + "cpd_${rootProject.name}"
def task = tasks.findByName(taskName)
if (task == null) {
task = tasks.create(taskName, tasks.findByName('cpdCheck').getClass().getSuperclass()) {
minimumTokenCount = 60
source = files(sources)
ignoreFailures = true
reports {
xml {
enabled = true
destination = file("${rootProject.buildDir}/reports/cpd.xml")
}
}
}
}
return task.path
}
getLintTask = { buildVariant ->
def appProject = subprojects.find { it.plugins.hasPlugin("com.android.application") }
def lintTaskPath
if (buildVariant != null) {
lintTaskPath = ":${appProject.name}:lint${buildVariant.name.capitalize()}"
} else {
def lintDebugTasks = appProject.tasks.matching { it.getName().contains("lint") && it.getName().contains("Debug") }
lintTaskPath = lintDebugTasks.first().path
}
if (lintTaskPath == null) {
throw IllegalStateException("Unable to find lint debug task for build variant: ${buildVariant}")
}
return lintTaskPath
}
getKotlinDetektTasks = {
subprojects
.findResults { it.tasks.findByName("detekt")?.path }
.findAll { !it.contains(":libs") }
}
task optimizePng {
doFirst {
def jarArgs = new ArrayList<String>()
jarArgs.add(configurations.pngtastic.asPath)
def relatedPathIndex = "${rootDir}".length() + 1
for (def file : fileTree(dir: "${rootDir}", include: '**/src/**/res/drawable**/*.png')) {
jarArgs.add(file.absolutePath.substring(relatedPathIndex))
}
for (def file : fileTree(dir: "${rootDir}", include: '**/src/**/res/mipmap**/*.png')) {
jarArgs.add(file.absolutePath.substring(relatedPathIndex))
}
javaexec { main = "-jar"; args = jarArgs; workingDir = file("${rootDir}") }
}
}
dependencies {
pngtastic 'com.github.depsypher:pngtastic:1.2'
}

20
gradle/plugins/.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# Generated files
bin/
gen/
# Gradle files
.gradle/
build/
/*/build/
# Local configuration file (sdk path, etc)
local.properties
# Log Files
*.log
.gradle
.idea
.DS_Store
/captures
*.iml

View File

@ -0,0 +1,56 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
`java-gradle-plugin`
`kotlin-dsl`
}
// The kotlin-dsl plugin requires a repository to be declared
repositories {
mavenCentral()
google()
}
dependencies {
// android gradle plugin, required by custom plugin
implementation("com.android.tools.build:gradle:4.0.1")
implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.10.0")
implementation("de.aaschmid:gradle-cpd-plugin:3.1")
// kotlin plugin, required by custom plugin
implementation(kotlin("gradle-plugin", embeddedKotlinVersion))
gradleKotlinDsl()
implementation(kotlin("stdlib-jdk8"))
}
val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "1.8"
}
gradlePlugin {
plugins {
create("api-generator-android") {
id = "api-generator-android"
implementationClass = "apigen.ApiGeneratorAndroidPlugin"
}
create("swagger-generator-android") {
id = "swagger-generator-android"
implementationClass = "apigen.SwaggerApiGeneratorAndroidPlugin"
}
create("api-generator-backend") {
id = "api-generator-backend"
implementationClass = "apigen.ApiGeneratorBackendPlugin"
}
create("static-analysis-android") {
id = "static-analysis-android"
implementationClass = "static_analysis.plugins.StaticAnalysisAndroidPlugin"
}
create("static-analysis-backend") {
id = "static-analysis-backend"
implementationClass = "static_analysis.plugins.StaticAnalysisBackendPlugin"
}
}
}

View File

View File

@ -0,0 +1,46 @@
package apigen
import com.android.build.gradle.AppExtension
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Project
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.kotlin.dsl.findByType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
class ApiGeneratorAndroidPlugin : ApiGeneratorPlugin() {
override fun apply(target: Project) {
super.apply(target)
with(target) {
val extension = getExtension()
val outputDir = getDirectoryForGeneration()
extension.outputDirPath = outputDir.path
extension.recreateOutputDir = true
afterEvaluate {
extensions.findByType<LibraryExtension>()?.apply {
sourceSets.getByName("main")
.java
.srcDir(outputDir)
}
extensions.findByType<AppExtension>()?.apply {
sourceSets.getByName("main")
.java
.srcDir(outputDir)
}
tasks
.filterIsInstance<KotlinCompile>()
.forEach { it.source(outputDir) }
tasks
.filterIsInstance<JavaCompile>()
.forEach { it.source(outputDir) }
}
}
}
private fun Project.getDirectoryForGeneration() = file("$buildDir/generated/api")
}

View File

@ -0,0 +1,17 @@
package apigen
import org.gradle.api.Project
class ApiGeneratorBackendPlugin : ApiGeneratorPlugin() {
override fun apply(target: Project) {
super.apply(target)
val extension = target.getExtension()
extension.outputDirPath = target.file("src/main/kotlin").path
extension.recreateOutputDir = false
extension.outputLanguage = OutputLanguage.KotlinServer
}
}

View File

@ -0,0 +1,22 @@
package apigen
open class ApiGeneratorExtension(
var pathToApiSchemes: String? = null,
var outputPackageName: String = "",
var outputDirPath: String = "",
var recreateOutputDir: Boolean = false,
var outputLanguage: OutputLanguage? = null
)
sealed class OutputLanguage(val argName: String, val methodOutputType: MethodOutputType? = null) {
object KotlinServer : OutputLanguage("KOTLIN_SERVER")
class KotlinAndroid(methodOutputType: MethodOutputType = MethodOutputType.Rx) : OutputLanguage("KOTLIN", methodOutputType)
object Java : OutputLanguage("JAVA")
object Swift : OutputLanguage("SWIFT")
}
sealed class MethodOutputType(val argName: String) {
object Rx : MethodOutputType("REACTIVE")
object RetrofitCall : MethodOutputType("CALL")
object Coroutine : MethodOutputType("COROUTINE")
}

View File

@ -0,0 +1,76 @@
package apigen
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.repositories
abstract class ApiGeneratorPlugin : Plugin<Project> {
companion object {
const val API_GENERATOR_CONFIG = "apiGenerator"
const val API_GENERATOR_EXT_NAME = "apiGenerator"
const val API_GENERATOR_DEFAULT_VERSION = "1.4.0-beta10"
}
override fun apply(target: Project) {
with(target) {
repositories {
maven {
url = uri("https://maven.dev.touchin.ru")
metadataSources {
artifact()
}
}
}
configurations.create(API_GENERATOR_CONFIG)
dependencies {
add(API_GENERATOR_CONFIG, "ru.touchin:api-generator:$API_GENERATOR_DEFAULT_VERSION")
}
extensions.create<ApiGeneratorExtension>(API_GENERATOR_EXT_NAME)
val apiGenTask = createApiGeneratorTask()
gradle.projectsEvaluated {
tasks.getByName("preBuild").dependsOn(apiGenTask)
}
}
}
protected fun Project.getExtension(): ApiGeneratorExtension = extensions.getByName(API_GENERATOR_EXT_NAME) as ApiGeneratorExtension
private fun Project.createApiGeneratorTask(): Task = tasks.create(API_GENERATOR_CONFIG).doLast {
val extension = getExtension()
val pathToApiSchemes = extension.pathToApiSchemes ?: throw IllegalStateException("Configure path to api schemes for api generator plugin")
val outputLanguage = extension.outputLanguage ?: throw IllegalStateException("Configure output language code for api generator plugin")
javaexec {
main = "-jar"
workingDir = rootDir
args = listOfNotNull(
configurations.getByName("apiGenerator").asPath,
"generate-client-code",
"--output-language",
outputLanguage.argName,
"--specification-path",
pathToApiSchemes,
"--kotlin-methods-generation-mode".takeIf { outputLanguage.methodOutputType != null },
outputLanguage.methodOutputType?.argName,
"--output-path",
extension.outputDirPath,
"--package-name",
extension.outputPackageName,
"--recreate_output_dirs",
extension.recreateOutputDir.toString()
)
}
}
}

View File

@ -0,0 +1,91 @@
package apigen
import org.gradle.api.Action
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.repositories
class SwaggerApiGeneratorAndroidPlugin : Plugin<Project> {
private companion object {
const val GENERATOR_CONFIG = "swaggerCodegen"
const val GENERATOR_VERSION = "3.0.34"
const val TI_GENERATOR_CONFIG = "TIKotlin-swagger-codegen"
const val TI_GENERATOR_VERSION = "1.0.0"
const val GENERATOR_EXT_NAME = "swaggerApiGenerator"
const val MAVEN_URL = "https://maven.dev.touchin.ru"
}
override fun apply(target: Project) {
with(target) {
repositories {
maven {
url = uri(MAVEN_URL)
metadataSources {
artifact()
}
}
}
configurations.create(GENERATOR_CONFIG)
configurations.create(TI_GENERATOR_CONFIG)
dependencies {
add(TI_GENERATOR_CONFIG, "ru.touchin:TIKotlin-swagger-codegen:$TI_GENERATOR_VERSION")
add(GENERATOR_CONFIG, "io.swagger.codegen.v3:swagger-codegen-cli:$GENERATOR_VERSION")
}
extensions.create<SwaggerApiGeneratorExtension>(GENERATOR_EXT_NAME)
val apiGenTask = createSwaggerApiGeneratorTask()
gradle.projectsEvaluated {
tasks.getByName("preBuild").dependsOn(apiGenTask)
}
}
}
protected fun Project.getExtension(): SwaggerApiGeneratorExtension = extensions.getByName(GENERATOR_EXT_NAME) as SwaggerApiGeneratorExtension
private fun Project.createSwaggerApiGeneratorTask(): Task = tasks.create(GENERATOR_CONFIG).doLast {
val extension = getExtension()
val taskWorkingDir = extension.taskWorkingDir ?: throw IllegalStateException("Configure taskWorkingDir for swagger generator plugin")
val apiSchemesFilePath = extension.apiSchemesFilePath ?: throw IllegalStateException("Configure sourceFilePath for swagger generator plugin")
val outputDir = extension.outputDir ?: throw IllegalStateException("Configure outputDir for swagger generator plugin")
val projectName = extension.projectName ?: throw IllegalStateException("Configure projectName for swagger generator plugin")
javaexec {
workingDir = file(taskWorkingDir)
classpath = files(configurations.getByName(GENERATOR_CONFIG).asPath,
configurations.getByName(TI_GENERATOR_CONFIG).asPath)
main = "io.swagger.codegen.v3.cli.SwaggerCodegen"
args = listOfNotNull(
"generate",
"-i",
apiSchemesFilePath,
"-l",
"TIKotlinCodegen",
"-o",
outputDir,
"--additional-properties",
"projectName=$projectName"
)
}
}
}
open class SwaggerApiGeneratorExtension(
var taskWorkingDir: String? = null,
var apiSchemesFilePath: String? = null,
var outputDir: String? = null,
var projectName: String? = null
)
fun Project.swaggerApiGenerator(configure: Action<SwaggerApiGeneratorExtension>): Unit =
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("swaggerApiGenerator", configure)

View File

@ -0,0 +1,15 @@
package static_analysis.errors
class AndroidLintError(
private val filePath: String,
private val fileLine: String?,
private val errorId: String,
private val description: String
) : StaticAnalysisError {
override fun print(count: Int): String = "\n$count. Android Lint. $description ($errorId)\n\tat [$filePath$fileLinePrefix]"
private val fileLinePrefix: String
get() = fileLine?.let { ":$it" }.orEmpty()
}

View File

@ -0,0 +1,12 @@
package static_analysis.errors
class CpdError(
private val duplications: List<Pair<String, String>>,
private val codeFragment: String
) : StaticAnalysisError {
override fun print(count: Int): String = "\n$count. CPD. Code duplication in files: " +
duplications.joinToString(separator = "") { (file, line) -> "\n\t[$file:$line]" } +
"\n\n Duplicated code:\n\n$codeFragment\n"
}

View File

@ -0,0 +1,12 @@
package static_analysis.errors
class DetektError(
private val filePath: String,
private val fileLine: String,
private val errorId: String,
private val description: String
) : StaticAnalysisError {
override fun print(count: Int): String = "\n$count. Detekt. $description ($errorId)\n\tat [$filePath:$fileLine]"
}

View File

@ -0,0 +1,5 @@
package static_analysis.errors
interface StaticAnalysisError {
fun print(count: Int): String
}

View File

@ -0,0 +1,72 @@
package static_analysis.linters
import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.findByType
import static_analysis.errors.AndroidLintError
import static_analysis.errors.StaticAnalysisError
import static_analysis.plugins.StaticAnalysisExtension
import static_analysis.utils.typedChildren
import static_analysis.utils.xmlParser
class AndroidLinter : Linter {
override val name: String = "Android lint"
override fun getErrors(project: Project): List<StaticAnalysisError> = xmlParser(project.getLintReportFile())
.typedChildren()
.filter { it.name() == "issue" && (it.attribute("severity") as String) == "Error" }
.map { errorNode ->
errorNode
.typedChildren()
.filter { it.name() == "location" }
.map { locationNode ->
AndroidLintError(
filePath = locationNode.attribute("file") as String,
fileLine = locationNode.attribute("line") as String?,
errorId = errorNode.attribute("id") as String,
description = errorNode.attribute("message") as String
)
}
}
.flatten()
override fun setupForProject(project: Project, extension: StaticAnalysisExtension) {
project.beforeEvaluate {
subprojects
.mapNotNull { it.extensions.findByType<AppExtension>() }
.first()
.lintOptions.apply {
isAbortOnError = false
isCheckAllWarnings = true
isWarningsAsErrors = false
xmlReport = true
htmlReport = false
isCheckDependencies = true
disable("MissingConstraints", "VectorRaster")
xmlOutput = getLintReportFile()
lintConfig = file("${extension.buildScriptDir}/static_analysis_configs/lint.xml")
}
}
}
override fun getTaskNames(project: Project, buildType: String?): List<String> {
if (buildType == null) {
throw IllegalStateException("Build type must not be null in android linter")
}
return project
.subprojects
.filter { it.plugins.hasPlugin(AppPlugin::class.java) }
.mapNotNull { subproject: Project ->
subproject
.tasks
.find { task -> task.name.contains(buildType, ignoreCase = true) && task.name.contains("lint") }
?.path
}
}
private fun Project.getLintReportFile() = file("${rootProject.buildDir}/reports/lint-report.xml")
}

View File

@ -0,0 +1,60 @@
package static_analysis.linters
import de.aaschmid.gradle.plugins.cpd.Cpd
import de.aaschmid.gradle.plugins.cpd.CpdExtension
import org.gradle.api.Project
import org.gradle.kotlin.dsl.findByType
import org.gradle.kotlin.dsl.withType
import static_analysis.errors.CpdError
import static_analysis.errors.StaticAnalysisError
import static_analysis.plugins.StaticAnalysisExtension
import static_analysis.utils.getSources
import static_analysis.utils.typedChildren
import static_analysis.utils.xmlParser
class CpdLinter : Linter {
override val name: String = "CPD"
override fun getErrors(project: Project): List<StaticAnalysisError> = xmlParser(project.getCpdReportFile())
.typedChildren()
.filter { it.name() == "duplication" }
.map { duplicationNode ->
val children = duplicationNode
.typedChildren()
CpdError(
duplications = children
.filter { it.name() == "file" }
.map { fileNode -> fileNode.attribute("path") as String to fileNode.attribute("line") as String },
codeFragment = children.findLast { it.name() == "codefragment" }!!.text()
)
}
override fun setupForProject(project: Project, extension: StaticAnalysisExtension) {
project.afterEvaluate {
extensions.findByType<CpdExtension>()!!.apply {
isSkipLexicalErrors = true
language = "kotlin"
minimumTokenCount = 60
}
tasks.withType<Cpd> {
reports.xml.required.set(true)
reports.xml.destination = getCpdReportFile()
ignoreFailures = true
source = getSources(extension.excludes)
}
}
}
override fun getTaskNames(project: Project, buildType: String?): List<String> = project
.rootProject
.tasks
.withType<Cpd>()
.map(Cpd::getPath)
private fun Project.getCpdReportFile() = file("${rootProject.buildDir}/reports/cpd.xml")
}

View File

@ -0,0 +1,62 @@
package static_analysis.linters
import io.gitlab.arturbosch.detekt.Detekt
import org.gradle.api.Project
import static_analysis.errors.DetektError
import static_analysis.errors.StaticAnalysisError
import static_analysis.plugins.StaticAnalysisExtension
import static_analysis.utils.getSources
import static_analysis.utils.typedChildren
import static_analysis.utils.xmlParser
class DetektLinter : Linter {
override val name: String = "Detekt"
override fun getErrors(project: Project): List<StaticAnalysisError> = xmlParser(project.getDetektReportFile())
.typedChildren()
.filter { fileNode -> fileNode.name() == "file" }
.map { fileNode ->
fileNode
.typedChildren()
.filter { it.name() == "error" }
.map { errorNode ->
DetektError(
filePath = fileNode.attribute("name") as String,
fileLine = errorNode.attribute("line") as String,
errorId = errorNode.attribute("source") as String,
description = errorNode.attribute("message") as String
)
}
}
.flatten()
override fun setupForProject(project: Project, extension: StaticAnalysisExtension) {
project.afterEvaluate {
tasks.withType(Detekt::class.java) {
exclude("**/test/**")
exclude("resources/")
exclude("build/")
exclude("tmp/")
jvmTarget = "1.8"
config.setFrom(files("${extension.buildScriptDir!!}/static_analysis_configs/detekt-config.yml"))
reports {
txt.enabled = false
html.enabled = false
xml {
enabled = true
destination = getDetektReportFile()
}
}
source = getSources(extension.excludes)
}
}
}
override fun getTaskNames(project: Project, buildType: String?): List<String> = listOf(":detekt")
private fun Project.getDetektReportFile() = file("${rootProject.buildDir}/reports/detekt.xml")
}

View File

@ -0,0 +1,12 @@
package static_analysis.linters
import org.gradle.api.Project
import static_analysis.errors.StaticAnalysisError
import static_analysis.plugins.StaticAnalysisExtension
interface Linter {
val name: String
fun getErrors(project: Project): List<StaticAnalysisError>
fun setupForProject(project: Project, extension: StaticAnalysisExtension)
fun getTaskNames(project: Project, buildType: String? = null): List<String>
}

View File

@ -0,0 +1,41 @@
package static_analysis.plugins
import com.android.build.gradle.AppExtension
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByType
import static_analysis.linters.AndroidLinter
import static_analysis.linters.CpdLinter
import static_analysis.linters.DetektLinter
import static_analysis.linters.Linter
class StaticAnalysisAndroidPlugin : StaticAnalysisPlugin() {
override fun createStaticAnalysisTasks(project: Project, linters: List<Linter>) {
project.subprojects {
if (plugins.hasPlugin("com.android.application")) {
extensions.getByType<AppExtension>().apply {
applicationVariants.forEach { variant ->
project.tasks.register("staticAnalysis${variant.name.capitalize()}") {
setupStaticAnalysisTask(linters, variant.name)
}
}
project.tasks.register("staticAnalysis") {
setupStaticAnalysisTask(
linters = linters,
buildVariant = applicationVariants.first { it.name.contains("Debug") }.name
)
}
}
}
}
}
override fun createLinters(): List<Linter> = listOf(
DetektLinter(),
CpdLinter(),
AndroidLinter()
)
}

View File

@ -0,0 +1,21 @@
package static_analysis.plugins
import org.gradle.api.Project
import static_analysis.linters.CpdLinter
import static_analysis.linters.DetektLinter
import static_analysis.linters.Linter
class StaticAnalysisBackendPlugin : StaticAnalysisPlugin() {
override fun createStaticAnalysisTasks(project: Project, linters: List<Linter>) {
project.tasks.register("staticAnalysis") {
setupStaticAnalysisTask(linters)
}
}
override fun createLinters(): List<Linter> = listOf(
CpdLinter(),
DetektLinter()
)
}

View File

@ -0,0 +1,6 @@
package static_analysis.plugins
open class StaticAnalysisExtension(
var excludes: String = "",
var buildScriptDir: String? = null
)

View File

@ -0,0 +1,45 @@
package static_analysis.plugins
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.getByType
import static_analysis.linters.Linter
import static_analysis.utils.ReportGenerator
abstract class StaticAnalysisPlugin : Plugin<Project> {
companion object {
const val DETEKT_ID = "io.gitlab.arturbosch.detekt"
const val CPD_ID = "de.aaschmid.cpd"
const val STATIC_ANALYSIS_EXT_NAME = "staticAnalysis"
}
override fun apply(target: Project) {
with(target) {
pluginManager.apply(CPD_ID)
pluginManager.apply(DETEKT_ID)
extensions.create<StaticAnalysisExtension>(STATIC_ANALYSIS_EXT_NAME)
val linters = createLinters()
linters.forEach { it.setupForProject(target, extensions.getByType()) }
gradle.projectsEvaluated {
createStaticAnalysisTasks(target, linters)
}
}
}
fun Task.setupStaticAnalysisTask(linters: List<Linter>, buildVariant: String? = null) {
doFirst { ReportGenerator.generate(linters, project) }
dependsOn(*(linters.map { it.getTaskNames(project, buildVariant) }.flatten().toTypedArray()))
}
abstract fun createLinters(): List<Linter>
abstract fun createStaticAnalysisTasks(project: Project, linters: List<Linter>)
}

View File

@ -0,0 +1,5 @@
package static_analysis.utils
import groovy.util.Node
fun Node.typedChildren() = children() as List<Node>

View File

@ -0,0 +1,27 @@
package static_analysis.utils
import org.gradle.api.Project
import org.gradle.api.file.FileTree
import java.io.File
fun Project.getSources(excludes: String): FileTree = files(
project
.rootProject
.subprojects
.filter { subproject -> subproject.subprojects.isEmpty() && !excludes.contains(subproject.path) }
.map { subproject -> subproject.file("${subproject.projectDir.path}/src/main") }
.filter { it.exists() && it.isDirectory }
.flatMap { srcDir ->
srcDir
.listFiles()
.orEmpty()
.flatMap {
listOf(
File(srcDir.path, "java"),
File(srcDir.path, "kotlin")
)
}
}
.filter { it.exists() && it.isDirectory }
.map { it.path }
).asFileTree

View File

@ -0,0 +1,52 @@
package static_analysis.utils
import org.gradle.api.Project
import static_analysis.errors.StaticAnalysisError
import static_analysis.linters.Linter
object ReportGenerator {
fun generate(linters: List<Linter>, project: Project) {
val groupedErrors = linters
.map { linter -> linter to linter.getErrors(project) }
val lintersResults = groupedErrors
.map { (linter, linterErrors) -> linter.name to linterErrors.size }
val allErrors = groupedErrors
.map(Pair<Linter, List<StaticAnalysisError>>::second)
.flatten()
val consoleReport = StringBuilder("\nSTATIC ANALYSIS ERRORS:").apply {
appendAllErrors(allErrors)
append("\nREPORT:\n")
appendReportsSummary(lintersResults)
appendOverallSummary(allErrors)
}
if (allErrors.isEmpty()) {
println(consoleReport)
} else {
throw Exception(consoleReport.toString())
}
}
private fun StringBuilder.appendAllErrors(errors: List<StaticAnalysisError>) = errors
.mapIndexed { index, staticAnalysisError -> staticAnalysisError.print(index + 1) }
.forEach { error -> append(error) }
private fun StringBuilder.appendReportsSummary(lintersResults: List<Pair<String, Int>>) = lintersResults
.forEach { this.appendSummary(it.first, it.second) }
private fun StringBuilder.appendOverallSummary(errors: List<StaticAnalysisError>) = appendSummary("Overall", errors.size)
private fun StringBuilder.appendSummary(header: String, quantityOfErrors: Int) {
assert(quantityOfErrors < 0)
append("\n$header: ")
append(if (quantityOfErrors == 0) "PASSED" else "FAILED ($quantityOfErrors errors)")
}
}

View File

@ -0,0 +1,6 @@
package static_analysis.utils
import groovy.util.XmlParser
import java.io.File
fun xmlParser(file: File) = XmlParser().parse(file)

View File

@ -2,11 +2,11 @@ import groovy.json.JsonSlurper
import groovy.xml.MarkupBuilder
task stringGenerator {
generate(android.languageMap)
generate(android.languageMap, project)
println("Strings generated!")
}
private def generate(Map<String, String> sources) {
private def generate(Map<String, String> sources, Project project) {
if (sources == null || sources.isEmpty()) {
throw new IOException("languageMap can't be null or empty")
}
@ -31,7 +31,7 @@ private def generate(Map<String, String> sources) {
}
}
def stringsFile = getFile(key, key == defaultLang)
def stringsFile = getFile(key, key == defaultLang, project)
stringsFile.write(sw.toString(), "UTF-8")
}
}
@ -69,15 +69,15 @@ private static Map<String, Object> getJsonsMap(Map sources) {
}
}
private static File getFile(String key, boolean defaultLang) {
private static File getFile(String key, boolean defaultLang, Project project) {
if (defaultLang) {
return new File("app/src/main/res/values/strings.xml")
return project.file("src/main/res/values/strings.xml")
} else {
def directory = new File("app/src/main/res/values-$key")
def directory = project.file("src/main/res/values-$key")
if (!directory.exists()) {
directory.mkdir()
}
return new File("app/src/main/res/values-$key/strings.xml")
return project.file("src/main/res/values-$key/strings.xml")
}
}

View File

@ -1,166 +0,0 @@
buildscript {
repositories {
maven { url "https://plugins.gradle.org/m2" }
}
dependencies {
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.5.1"
}
}
def getServerProjectSources
def getAndroidProjectSources
apply from: "$buildScriptsDir/gradle/commonStaticAnalysis.gradle"
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs <<
"-Xlint:cast" <<
"-Xlint:divzero" <<
"-Xlint:empty" <<
"-Xlint:deprecation" <<
"-Xlint:finally" <<
"-Xlint:overrides" <<
"-Xlint:path" <<
"-Werror"
}
def excludes = rootProject.extensions.findByName("staticAnalysisExcludes")
def androidSources = getAndroidProjectSources(excludes)
def androidStaticAnalysisTasks = getStaticAnalysisTaskNames(true, androidSources, null)
def androidIdeaFormatTask = getIdeaFormatTask(true, androidSources)
task staticAnalysisWithFormatting {
androidStaticAnalysisTasks.each { task ->
tasks.findByName(task)?.mustRunAfter(androidIdeaFormatTask)
}
dependsOn androidIdeaFormatTask
dependsOn androidStaticAnalysisTasks
doFirst {
generateReport(true)
}
}
task staticAnalysis {
dependsOn androidStaticAnalysisTasks
doFirst {
generateReport(true)
}
}
def serverStaticAnalysisTasks = getStaticAnalysisTaskNames(false, getServerProjectSources(excludes), null)
def serverIdeaFormatTask = getIdeaFormatTask(false, getServerProjectSources(excludes))
task serverStaticAnalysisWithFormatting {
serverStaticAnalysisTasks.each { task ->
tasks.findByName(task)?.mustRunAfter(serverIdeaFormatTask)
}
dependsOn serverIdeaFormatTask
dependsOn serverStaticAnalysisTasks
doFirst {
generateReport(false)
}
}
task serverStaticAnalysis {
dependsOn serverStaticAnalysisTasks
doFirst {
generateReport(false)
}
}
subprojects { subproject ->
if (subproject.plugins.hasPlugin("com.android.application")) {
subproject.android {
lintOptions.abortOnError = false
lintOptions.checkAllWarnings = true
lintOptions.warningsAsErrors = false
lintOptions.xmlReport = true
lintOptions.xmlOutput = file "$rootProject.buildDir/reports/lint_report.xml"
lintOptions.htmlReport = false
lintOptions.lintConfig = file "$buildScriptsDir/lint/lint.xml"
lintOptions.checkDependencies true
lintOptions.disable 'MissingConstraints', 'VectorRaster'
applicationVariants.all { variant ->
task("staticAnalysis${variant.name.capitalize()}") {
dependsOn getStaticAnalysisTaskNames(true, androidSources, variant)
doFirst { generateReport(true) }
}
}
}
}
def regex = ~':detekt$'
tasks.forEach { task ->
if (!task.name.contains(":libs") && task.path =~ regex) {
task.exclude '**/test/**'
task.exclude 'resources/'
task.exclude 'build/'
task.exclude 'tmp/'
task.jvmTarget = "1.8"
}
}
detekt {
config = files("$buildScriptsDir/kotlin/detekt-config.yml")
reports {
txt.enabled = false
html.enabled = false
xml {
enabled = true
destination = file("${rootProject.buildDir}/reports/kotlin-detekt-${subproject.name}.xml")
}
}
}
}
}
getServerProjectSources = { excludes ->
def sources = new ArrayList<String>()
def sourcesDirectory = new File(project.projectDir.path, 'src')
for (def sourceFlavorDirectory : sourcesDirectory.listFiles()) {
def javaSourceDirectory = new File(sourceFlavorDirectory.path, 'java')
def kotlinSourceDirectory = new File(sourceFlavorDirectory.path, 'kotlin')
if (javaSourceDirectory.exists() && javaSourceDirectory.isDirectory()) {
sources.add(javaSourceDirectory.absolutePath)
}
if (kotlinSourceDirectory.exists() && kotlinSourceDirectory.isDirectory()) {
sources.add(kotlinSourceDirectory.absolutePath)
}
}
return sources
}
getAndroidProjectSources = { excludes ->
def sources = new ArrayList<String>()
for (def project : rootProject.subprojects) {
if (!project.subprojects.isEmpty() || (excludes != null && excludes.contains(project.path))) {
continue
}
def sourcesDirectory = new File(project.projectDir.path, 'src')
if (!sourcesDirectory.exists() || !sourcesDirectory.isDirectory()) {
continue
}
for (def sourceFlavorDirectory : sourcesDirectory.listFiles()) {
def javaSourceDirectory = new File(sourceFlavorDirectory.path, 'java')
def kotlinSourceDirectory = new File(sourceFlavorDirectory.path, 'kotlin')
if (javaSourceDirectory.exists() && javaSourceDirectory.isDirectory()) {
sources.add(javaSourceDirectory.absolutePath)
}
if (kotlinSourceDirectory.exists() && kotlinSourceDirectory.isDirectory()) {
sources.add(kotlinSourceDirectory.absolutePath)
}
}
}
return sources
}

View File

@ -1,272 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="InvalidPackage" severity="error">
<ignore regexp="Invalid package reference in library.*not included in Android.*java.*"/>
</issue>
<issue id="GradleCompatible" severity="error">
<ignore regexp="All com.android.support libraries must use the exact same version specification (mixing versions can lead to runtime crashes). Found versions *. Examples include *"/>
</issue>
<!-- todo: lint bug? -->
<issue id="MissingPermission" severity="ignore"/>
<!-- todo: lint bug? -->
<issue id="MissingSuperCall" severity="ignore"/>
<!--All activities should have locked orientation-->
<issue id="LockedOrientationActivity" severity="ignore"/>
<issue id="AllowAllHostnameVerifier" severity="error"/>
<issue id="InvalidUsesTagAttribute" severity="error"/>
<issue id="MissingIntentFilterForMediaSearch" severity="error"/>
<issue id="MissingMediaBrowserServiceIntentFilter" severity="error"/>
<issue id="MissingOnPlayFromSearch" severity="error"/>
<issue id="ShiftFlags" severity="error"/>
<issue id="LocalSuppress" severity="error"/>
<issue id="SwitchIntDef" severity="error"/>
<issue id="UniqueConstants" severity="error"/>
<issue id="InlinedApi" severity="error"/>
<issue id="Override" severity="error"/>
<issue id="UnusedAttribute" severity="warning"/>
<issue id="AppCompatResource" severity="error"/>
<issue id="BadHostnameVerifier" severity="error"/>
<issue id="EnforceUTF8" severity="error"/>
<issue id="ByteOrderMark" severity="error"/>
<issue id="StopShip" severity="error"/>
<issue id="MissingPrefix" severity="error"/>
<issue id="MangledCRLF" severity="error"/>
<issue id="DuplicateIds" severity="error"/>
<issue id="DuplicateDefinition" severity="error"/>
<issue id="ReferenceType" severity="error"/>
<issue id="UnsafeDynamicallyLoadedCode" severity="error"/>
<issue id="UnsafeNativeCodeLocation" severity="error"/>
<issue id="FullBackupContent" severity="error"/>
<issue id="ValidFragment" severity="error"/>
<issue id="GradleCompatible" severity="error"/>
<issue id="GradlePluginVersion" severity="error"/>
<issue id="GradleGetter" severity="error"/>
<issue id="GradleIdeError" severity="error"/>
<issue id="NotInterpolated" severity="error"/>
<issue id="StringShouldBeInt" severity="error"/>
<issue id="AccidentalOctal" severity="error"/>
<issue id="GridLayout" severity="error"/>
<issue id="HardcodedDebugMode" severity="error"/>
<issue id="IconExpectedSize" severity="error"/>
<issue id="IncludeLayoutParam" severity="error"/>
<issue id="TrustAllX509TrustManager" severity="warning"/>
<issue id="DefaultLocale" severity="error"/>
<issue id="InvalidResourceFolder" severity="error"/>
<issue id="LongLogTag" severity="error"/>
<issue id="LogTagMismatch" severity="error"/>
<issue id="DuplicateActivity" severity="error"/>
<issue id="MockLocation" severity="error"/>
<issue id="MultipleUsesSdk" severity="error"/>
<issue id="UniquePermission" severity="error"/>
<issue id="WrongManifestParent" severity="error"/>
<issue id="ManifestResource" severity="error"/>
<issue id="ManifestTypo" severity="error"/>
<issue id="Instantiatable" severity="error"/>
<issue id="LibraryCustomView" severity="error"/>
<issue id="ResAuto" severity="error"/>
<issue id="NamespaceTypo" severity="error"/>
<issue id="UnusedNamespace" severity="error"/>
<issue id="NfcTechWhitespace" severity="error"/>
<issue id="OnClick" severity="error"/>
<issue id="DalvikOverride" severity="error"/>
<issue id="OverrideAbstract" severity="error"/>
<issue id="ParcelCreator" severity="error"/>
<issue id="PackagedPrivateKey" severity="error"/>
<issue id="Proguard" severity="error"/>
<issue id="PropertyEscape" severity="error"/>
<issue id="ParcelClassLoader" severity="error"/>
<issue id="RecyclerView" severity="error"/>
<issue id="RequiredSize" severity="error"/>
<issue id="AaptCrash" severity="error"/>
<issue id="ResourceCycle" severity="error"/>
<issue id="ResourceName" severity="error"/>
<issue id="ValidRestrictions" severity="error"/>
<issue id="RtlCompat" severity="error"/>
<issue id="ScrollViewSize" severity="error"/>
<issue id="SecureRandom" severity="error"/>
<issue id="SetWorldReadable" severity="error"/>
<issue id="SetWorldWritable" severity="error"/>
<issue id="ServiceCast" severity="error"/>
<issue id="SSLCertificateSocketFactoryCreateSocket" severity="error"/>
<issue id="SSLCertificateSocketFactoryGetInsecure" severity="error"/>
<issue id="StringFormatMatches" severity="error"/>
<issue id="StringFormatInvalid" severity="error"/>
<issue id="UseCheckPermission" severity="error"/>
<issue id="CheckResult" severity="warning"/>
<issue id="ResourceAsColor" severity="error"/>
<issue id="Range" severity="error"/>
<issue id="ResourceType" severity="warning"/>
<issue id="WrongThread" severity="error"/>
<issue id="WrongConstant" severity="error"/>
<issue id="ProtectedPermissions" severity="error"/>
<issue id="MenuTitle" severity="error"/>
<issue id="ExtraTranslation" severity="error"/>
<issue id="MissingLeanbackLauncher" severity="error"/>
<issue id="MissingLeanbackSupport" severity="error"/>
<issue id="PermissionImpliesUnsupportedHardware" severity="error"/>
<issue id="UnsupportedTvHardware" severity="error"/>
<issue id="MissingTvBanner" severity="error"/>
<issue id="TypographyOther" severity="error"/>
<issue id="TypographyQuotes" severity="warning"/>
<issue id="UnsafeProtectedBroadcastReceiver" severity="error"/>
<issue id="UnprotectedSMSBroadcastReceiver" severity="error"/>
<issue id="VectorRaster" severity="error"/>
<issue id="ViewTag" severity="error"/>
<issue id="WrongViewCast" severity="error"/>
<issue id="Wakelock" severity="error"/>
<issue id="WrongCall" severity="error"/>
<issue id="WrongCase" severity="error"/>
<issue id="InvalidId" severity="error"/>
<issue id="NotSibling" severity="error"/>
<issue id="UnknownIdInLayout" severity="error"/>
<issue id="WrongFolder" severity="error"/>
<issue id="AdapterViewChildren" severity="error"/>
<issue id="SetJavaScriptEnabled" severity="warning"/>
<issue id="AddJavascriptInterface" severity="warning"/>
<issue id="AllowBackup" severity="error"/>
<issue id="AlwaysShowAction" severity="error"/>
<issue id="AppCompatMethod" severity="error"/>
<issue id="Assert" severity="error"/>
<issue id="CommitPrefEdits" severity="error"/>
<issue id="CommitTransaction" severity="error"/>
<issue id="CustomViewStyleable" severity="error"/>
<issue id="CutPasteId" severity="error"/>
<issue id="Deprecated" severity="error"/>
<issue id="DeviceAdmin" severity="error"/>
<issue id="DisableBaselineAlignment" severity="error"/>
<issue id="DrawAllocation" severity="warning"/>
<issue id="DuplicateIncludedIds" severity="error"/>
<issue id="DuplicateUsesFeature" severity="error"/>
<issue id="ExportedContentProvider" severity="error"/>
<issue id="ExportedReceiver" severity="error"/>
<issue id="ExportedService" severity="error"/>
<issue id="ExtraText" severity="error"/>
<issue id="GetInstance" severity="error"/>
<issue id="GifUsage" severity="warning"/>
<issue id="GradleDependency" severity="warning"/>
<issue id="GradleDeprecated" severity="error"/>
<issue id="GradleDynamicVersion" severity="error"/>
<issue id="GradleOverrides" severity="error"/>
<issue id="GradlePath" severity="warning"/>
<issue id="GrantAllUris" severity="error"/>
<issue id="HandlerLeak" severity="error"/>
<issue id="HardcodedText" severity="error"/>
<issue id="IconColors" severity="warning"/>
<issue id="IconDensities" severity="warning"/>
<issue id="IconDipSize" severity="error"/>
<issue id="IconDuplicates" severity="error"/>
<issue id="IconDuplicatesConfig" severity="error"/>
<issue id="IconExtension" severity="error"/>
<issue id="IconLocation" severity="error"/>
<issue id="IconMixedNinePatch" severity="error"/>
<issue id="IconNoDpi" severity="error"/>
<issue id="IconXmlAndPng" severity="error"/>
<issue id="IllegalResourceRef" severity="error"/>
<issue id="InOrMmUsage" severity="error"/>
<issue id="InconsistentArrays" severity="error"/>
<issue id="InefficientWeight" severity="warning"/>
<issue id="InflateParams" severity="warning"/>
<issue id="InnerclassSeparator" severity="error"/>
<issue id="LocaleFolder" severity="error"/>
<issue id="LogConditional" severity="error"/>
<issue id="ManifestOrder" severity="error"/>
<issue id="MipmapIcons" severity="error"/>
<issue id="MissingApplicationIcon" severity="error"/>
<issue id="MissingId" severity="error"/>
<issue id="MissingVersion" severity="error"/>
<issue id="NestedWeights" severity="warning"/>
<issue id="NewerVersionAvailable" severity="warning"/>
<issue id="ObsoleteLayoutParam" severity="error"/>
<issue id="OldTargetApi" severity="error"/>
<issue id="PackageManagerGetSignatures" severity="error"/>
<issue id="PrivateResource" severity="error"/>
<issue id="ProguardSplit" severity="error"/>
<issue id="PxUsage" severity="error"/>
<issue id="Recycle" severity="error"/>
<issue id="Registered" severity="error"/>
<issue id="RtlEnabled" severity="error"/>
<issue id="SQLiteString" severity="error"/>
<issue id="ScrollViewCount" severity="error"/>
<issue id="ScrollViewSize" severity="error"/>
<issue id="SdCardPath" severity="error"/>
<issue id="ShortAlarm" severity="warning"/>
<issue id="ShowToast" severity="error"/>
<issue id="SignatureOrSystemPermissions" severity="error"/>
<issue id="SimpleDateFormat" severity="error"/>
<issue id="SpUsage" severity="error"/>
<issue id="StateListReachable" severity="error"/>
<issue id="SuspiciousImport" severity="error"/>
<issue id="TextFields" severity="error"/>
<issue id="TextViewEdits" severity="error"/>
<issue id="TooDeepLayout" severity="warning"/>
<issue id="TooManyViews" severity="warning"/>
<issue id="TypographyDashes" severity="warning"/>
<issue id="UnknownIdInLayout" severity="error"/>
<issue id="UnlocalizedSms" severity="error"/>
<issue id="UnusedIds" severity="error"/>
<issue id="UseAlpha2" severity="error"/>
<issue id="UseSparseArrays" severity="warning"/>
<issue id="UseValueOf" severity="error"/>
<issue id="UsesMinSdkAttributes" severity="error"/>
<issue id="UsingHttp" severity="warning"/>
<issue id="WorldReadableFiles" severity="warning"/>
<issue id="WorldWriteableFiles" severity="warning"/>
<issue id="WrongRegion" severity="warning"/>
<!-- DISABLE RULES BELOW -->
<issue id="GoogleAppIndexingApiWarning" severity="ignore"/>
<issue id="GoogleAppIndexingWarning" severity="ignore"/>
<issue id="AppLinksAutoVerifyError" severity="ignore"/>
<issue id="AppLinksAutoVerifyWarning" severity="ignore"/>
<issue id="ClickableViewAccessibility" severity="ignore"/>
<issue id="SetTextI18n" severity="ignore"/>
<issue id="EasterEgg" severity="ignore"/>
<issue id="BackButton" severity="ignore"/>
<issue id="FieldGetter" severity="ignore"/>
<issue id="Orientation" severity="ignore"/>
<issue id="Suspicious0dp" severity="ignore"/>
<issue id="NegativeMargin" severity="ignore"/>
<issue id="MissingQuantity" severity="ignore"/>
<issue id="ImpliedQuantity" severity="ignore"/>
<issue id="TrulyRandom" severity="ignore"/>
<issue id="SelectableText" severity="ignore"/>
<issue id="UnusedIds" severity="ignore"/>
<issue id="ButtonCase" severity="ignore"/>
<issue id="ButtonOrder" severity="ignore"/>
<issue id="ButtonStyle" severity="ignore"/>
<issue id="ContentDescription" severity="ignore"/>
<issue id="ExportedPreferenceActivity" severity="ignore"/>
<!-- remove when facebook will update support library in sdk -->
<issue id="GradleCompatible" severity="ignore"/>
<issue id="IconLauncherShape" severity="ignore"/>
<issue id="IconMissingDensityFolder" severity="ignore"/>
<issue id="InconsistentLayout" severity="ignore"/>
<issue id="LabelFor" severity="ignore"/>
<issue id="MissingTranslation" severity="ignore"/>
<issue id="NestedScrolling" severity="ignore"/>
<issue id="MergeRootFrame" severity="ignore"/>
<issue id="NewApi" severity="ignore"/>
<issue id="Overdraw" severity="ignore"/>
<issue id="PluralsCandidate" severity="ignore"/>
<issue id="RelativeOverlap" severity="ignore"/>
<issue id="RtlHardcoded" severity="ignore"/>
<issue id="RtlSymmetry" severity="ignore"/>
<issue id="SmallSp" severity="ignore"/>
<issue id="StringFormatCount" severity="ignore"/>
<issue id="TypographyFractions" severity="ignore"/>
<issue id="Typos" severity="ignore"/>
<issue id="UnusedQuantity" severity="ignore"/>
<issue id="UnusedResources" severity="ignore"/>
<issue id="UseCompoundDrawables" severity="ignore"/>
<issue id="UselessLeaf" severity="ignore"/>
<issue id="UselessParent" severity="ignore"/>
<issue id="ViewConstructor" severity="ignore"/>
<issue id="ViewHolder" severity="ignore"/>
<issue id="WebViewLayout" severity="ignore"/>
<issue id="TypographyEllipsis" severity="ignore"/>
<issue id="PermissionImpliesUnsupportedChromeOsHardware" severity="ignore"/>
</lint>

View File

@ -1,131 +0,0 @@
<?xml version="1.0"?>
<ruleset name="All Java Rules"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
<description>Every Java Rule in PMD</description>
<rule ref="rulesets/java/android.xml"/>
<rule ref="rulesets/java/basic.xml">
<exclude name="SimplifiedTernary"/>
</rule>
<rule ref="rulesets/java/braces.xml"/>
<rule ref="rulesets/java/clone.xml"/>
<rule ref="rulesets/java/codesize.xml"/>
<rule ref="rulesets/java/codesize.xml/TooManyMethods">
<properties>
<property name="maxmethods" value="20"/>
</properties>
</rule>
<rule ref="rulesets/java/codesize.xml/CyclomaticComplexity">
<properties>
<property name="showClassesComplexity" value="false"/>
<property name="violationSuppressXPath" value="./MethodDeclarator[@Image='hashCode' or @Image='equals']"/>
</properties>
</rule>
<rule ref="rulesets/java/codesize.xml/StdCyclomaticComplexity">
<properties>
<property name="showClassesComplexity" value="false"/>
<property name="violationSuppressXPath" value="./MethodDeclarator[@Image='hashCode' or @Image='equals']"/>
</properties>
</rule>
<rule ref="rulesets/java/codesize.xml/ModifiedCyclomaticComplexity">
<properties>
<property name="showClassesComplexity" value="false"/>
<property name="violationSuppressXPath" value="./MethodDeclarator[@Image='hashCode' or @Image='equals']"/>
</properties>
</rule>
<rule ref="rulesets/java/codesize.xml/NPathComplexity">
<properties>
<property name="violationSuppressXPath" value="./MethodDeclarator[@Image='hashCode' or @Image='equals']"/>
</properties>
</rule>
<rule ref="rulesets/java/comments.xml">
<exclude name="CommentSize"/>
<exclude name="CommentRequired"/>
<exclude name="CommentDefaultAccessModifier"/>
</rule>
<rule ref="rulesets/java/controversial.xml">
<exclude name="AtLeastOneConstructor"/>
<exclude name="OnlyOneReturn"/>
<exclude name="UseConcurrentHashMap"/>
<exclude name="NullAssignment"/>
<exclude name="DataflowAnomalyAnalysis"/>
<exclude name="AvoidFinalLocalVariable"/>
</rule>
<rule ref="rulesets/java/controversial.xml/AvoidLiteralsInIfCondition">
<properties>
<property name="ignoreMagicNumbers" value="-1,0,1"/>
</properties>
</rule>
<rule ref="rulesets/java/coupling.xml">
<exclude name="LoosePackageCoupling"/>
<exclude name="LawOfDemeter"/>
<exclude name="LooseCoupling"/>
</rule>
<rule ref="rulesets/java/coupling.xml/ExcessiveImports">
<properties>
<property name="minimum" value="50"/>
</properties>
</rule>
<rule ref="rulesets/java/design.xml">
<exclude name="GodClass"/>
<exclude name="ConfusingTernary"/>
<exclude name="ReturnEmptyArrayRatherThanNull"/>
<exclude name="FieldDeclarationsShouldBeAtStartOfClass"/>
<exclude name="EmptyMethodInAbstractClassShouldBeAbstract"/>
</rule>
<rule ref="rulesets/java/empty.xml"/>
<rule ref="rulesets/java/empty.xml/EmptyCatchBlock">
<properties>
<property name="allowCommentedBlocks" value="true"/>
</properties>
</rule>
<rule ref="rulesets/java/finalizers.xml"/>
<rule ref="rulesets/java/imports.xml"/>
<rule ref="rulesets/java/j2ee.xml">
<exclude name="DoNotUseThreads"/>
</rule>
<rule ref="rulesets/java/javabeans.xml">
<exclude name="BeanMembersShouldSerialize"/>
</rule>
<rule ref="rulesets/java/junit.xml"/>
<rule ref="rulesets/java/logging-jakarta-commons.xml">
<exclude name="GuardLogStatement"/>
</rule>
<rule ref="rulesets/java/logging-java.xml"/>
<rule ref="rulesets/java/migrating.xml"/>
<rule ref="rulesets/java/naming.xml">
<exclude name="SuspiciousEqualsMethodName"/>
<exclude name="AbstractNaming"/>
<exclude name="ShortVariable"/>
<exclude name="LongVariable"/>
<exclude name="GenericsNaming"/>
</rule>
<rule ref="rulesets/java/naming.xml/ShortClassName">
<properties>
<property name="minimum" value="4"/>
</properties>
</rule>
<rule ref="rulesets/java/optimizations.xml">
<exclude name="AvoidInstantiatingObjectsInLoops"/>
</rule>
<rule ref="rulesets/java/strictexception.xml">
<exclude name="AvoidCatchingGenericException"/>
<exclude name="SignatureDeclareThrowsException"/>
</rule>
<rule ref="rulesets/java/strings.xml"/>
<rule ref="rulesets/java/sunsecure.xml">
<exclude name="MethodReturnsInternalArray"/>
<exclude name="ArrayIsStoredDirectly"/>
</rule>
<rule ref="rulesets/java/typeresolution.xml">
<exclude name="SignatureDeclareThrowsException"/>
<exclude name="LooseCoupling"/>
</rule>
<rule ref="rulesets/java/unnecessary.xml">
<exclude name="UselessParentheses"/>
</rule>
<rule ref="rulesets/java/unusedcode.xml"/>
</ruleset>

View File

@ -1,5 +1,4 @@
-include rules/components.pro
-include rules/okhttp.pro
-include rules/retrofit.pro
-include rules/logansquare.pro
@ -8,4 +7,3 @@
-include rules/kaspersky.pro
-include rules/appsflyer.pro
-include rules/moshi.pro
-include rules/androidx_security.pro

View File

@ -6,6 +6,7 @@
}
-keep @com.squareup.moshi.JsonQualifier interface *
-keep @com.squareup.moshi.JsonQualifier class *
# Enum field names are used by the integrated EnumJsonAdapter.
# values() is synthesized by the Kotlin compiler and is used by EnumJsonAdapter indirectly

71
scripts/export_src.sh Executable file
View File

@ -0,0 +1,71 @@
#!/bin/sh
# Description:
# Creates archive with source code of multiple repositories.
#
# Parameters:
# $1 - github repository name without suffix (project name).
# $2, $3, ..., $n - repository suffixes (platforms).
#
# Optional environment variables:
# GIT_BRANCH - branch to use. Default - master.
#
# Example of usage:
# export_src.sh TestProject ios android backend
# GIT_BRANCH="develop" ./export_src.sh TestProject ios web
#
if [ -z "${GIT_BRANCH}" ]; then
GIT_BRANCH="master"
fi
LAST_COMMIT_DATE=""
PROJECT_NAME=$1
SRC_FOLDER_NAME="${PROJECT_NAME}-src"
SRC_DIR="./${SRC_FOLDER_NAME}"
COMMAND_LINE_ARGUMENTS=$@
clone_platform() {
PROJECT_NAME=$1
PLATFORM=$2
if git clone --recurse-submodules -j8 "git@gitlab.ti:touchinstinct/${PROJECT_NAME}-${PLATFORM}.git" --branch "${GIT_BRANCH}"; then
cd ${PROJECT_NAME}-${PLATFORM}
COMMIT_DATE=`git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d'`
if [[ $LAST_COMMIT_DATE < $COMMIT_DATE ]]; then
LAST_COMMIT_DATE="${COMMIT_DATE}"
fi
cd ..
else
exit 1
fi
}
mkdir -p "${SRC_DIR}"
cd "${SRC_DIR}"
for argument in ${COMMAND_LINE_ARGUMENTS}
do
if [ $argument != $PROJECT_NAME ]; then
platform=${argument} # all arguments after project name treated as platforms
clone_platform ${PROJECT_NAME} ${platform}
fi
done
ERR_PATHS=$(find . -name "*[<>:\\|?*]*" | xargs -I %s echo "- %s")
if [ "$ERR_PATHS" ]; then
echo "Export aborted! Invalid characters found in file or directories name(s):\n$ERR_PATHS"
exit 1
fi
if [ -z "${EXPORT_DATE}" ]; then
EXPORT_DATE="${LAST_COMMIT_DATE}"
fi
find . -name ".git*" -print0 | xargs -0 rm -rf
zip -r -q "${SRC_FOLDER_NAME}-${EXPORT_DATE}".zip .
open .

View File

@ -23,7 +23,7 @@ formatting:
active: true
console-reports:
active: true
active: false
exclude:
# - 'ProjectStatisticsReport'
# - 'ComplexityReport'
@ -387,9 +387,6 @@ style:
UnusedPrivateMember:
active: true
allowedNames: "(_|ignored|expected|serialVersionUID)"
UseDataClass:
active: true
excludeAnnotatedClasses: ""
UtilityClassWithPublicConstructor:
active: false
VarCouldBeVal:

View File

@ -0,0 +1,273 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="InvalidPackage" severity="error">
<ignore regexp="Invalid package reference in library.*not included in Android.*java.*" />
</issue>
<issue id="GradleCompatible" severity="error">
<ignore
regexp="All com.android.support libraries must use the exact same version specification (mixing versions can lead to runtime crashes). Found versions *. Examples include *" />
</issue>
<!-- todo: lint bug? -->
<issue id="MissingPermission" severity="ignore" />
<!-- todo: lint bug? -->
<issue id="MissingSuperCall" severity="ignore" />
<!--All activities should have locked orientation-->
<issue id="LockedOrientationActivity" severity="ignore" />
<issue id="AllowAllHostnameVerifier" severity="error" />
<issue id="InvalidUsesTagAttribute" severity="error" />
<issue id="MissingIntentFilterForMediaSearch" severity="error" />
<issue id="MissingMediaBrowserServiceIntentFilter" severity="error" />
<issue id="MissingOnPlayFromSearch" severity="error" />
<issue id="ShiftFlags" severity="error" />
<issue id="LocalSuppress" severity="error" />
<issue id="SwitchIntDef" severity="error" />
<issue id="UniqueConstants" severity="error" />
<issue id="InlinedApi" severity="error" />
<issue id="Override" severity="error" />
<issue id="UnusedAttribute" severity="warning" />
<issue id="AppCompatResource" severity="error" />
<issue id="BadHostnameVerifier" severity="error" />
<issue id="EnforceUTF8" severity="error" />
<issue id="ByteOrderMark" severity="error" />
<issue id="StopShip" severity="error" />
<issue id="MissingPrefix" severity="error" />
<issue id="MangledCRLF" severity="error" />
<issue id="DuplicateIds" severity="error" />
<issue id="DuplicateDefinition" severity="error" />
<issue id="ReferenceType" severity="error" />
<issue id="UnsafeDynamicallyLoadedCode" severity="error" />
<issue id="UnsafeNativeCodeLocation" severity="error" />
<issue id="FullBackupContent" severity="error" />
<issue id="ValidFragment" severity="error" />
<issue id="GradleCompatible" severity="error" />
<issue id="GradlePluginVersion" severity="error" />
<issue id="GradleGetter" severity="error" />
<issue id="GradleIdeError" severity="error" />
<issue id="NotInterpolated" severity="error" />
<issue id="StringShouldBeInt" severity="error" />
<issue id="AccidentalOctal" severity="error" />
<issue id="GridLayout" severity="error" />
<issue id="HardcodedDebugMode" severity="error" />
<issue id="IconExpectedSize" severity="error" />
<issue id="IncludeLayoutParam" severity="error" />
<issue id="TrustAllX509TrustManager" severity="warning" />
<issue id="DefaultLocale" severity="error" />
<issue id="InvalidResourceFolder" severity="error" />
<issue id="LongLogTag" severity="error" />
<issue id="LogTagMismatch" severity="error" />
<issue id="DuplicateActivity" severity="error" />
<issue id="MockLocation" severity="error" />
<issue id="MultipleUsesSdk" severity="error" />
<issue id="UniquePermission" severity="error" />
<issue id="WrongManifestParent" severity="error" />
<issue id="ManifestResource" severity="error" />
<issue id="ManifestTypo" severity="error" />
<issue id="Instantiatable" severity="error" />
<issue id="LibraryCustomView" severity="error" />
<issue id="ResAuto" severity="error" />
<issue id="NamespaceTypo" severity="error" />
<issue id="UnusedNamespace" severity="error" />
<issue id="NfcTechWhitespace" severity="error" />
<issue id="OnClick" severity="error" />
<issue id="DalvikOverride" severity="error" />
<issue id="OverrideAbstract" severity="error" />
<issue id="ParcelCreator" severity="error" />
<issue id="PackagedPrivateKey" severity="error" />
<issue id="Proguard" severity="error" />
<issue id="PropertyEscape" severity="error" />
<issue id="ParcelClassLoader" severity="error" />
<issue id="RecyclerView" severity="error" />
<issue id="RequiredSize" severity="error" />
<issue id="AaptCrash" severity="error" />
<issue id="ResourceCycle" severity="error" />
<issue id="ResourceName" severity="error" />
<issue id="ValidRestrictions" severity="error" />
<issue id="RtlCompat" severity="error" />
<issue id="ScrollViewSize" severity="error" />
<issue id="SecureRandom" severity="error" />
<issue id="SetWorldReadable" severity="error" />
<issue id="SetWorldWritable" severity="error" />
<issue id="ServiceCast" severity="error" />
<issue id="SSLCertificateSocketFactoryCreateSocket" severity="error" />
<issue id="SSLCertificateSocketFactoryGetInsecure" severity="error" />
<issue id="StringFormatMatches" severity="error" />
<issue id="StringFormatInvalid" severity="error" />
<issue id="UseCheckPermission" severity="error" />
<issue id="CheckResult" severity="warning" />
<issue id="ResourceAsColor" severity="error" />
<issue id="Range" severity="error" />
<issue id="ResourceType" severity="warning" />
<issue id="WrongThread" severity="error" />
<issue id="WrongConstant" severity="error" />
<issue id="ProtectedPermissions" severity="error" />
<issue id="MenuTitle" severity="error" />
<issue id="ExtraTranslation" severity="error" />
<issue id="MissingLeanbackLauncher" severity="error" />
<issue id="MissingLeanbackSupport" severity="error" />
<issue id="PermissionImpliesUnsupportedHardware" severity="error" />
<issue id="UnsupportedTvHardware" severity="error" />
<issue id="MissingTvBanner" severity="error" />
<issue id="TypographyOther" severity="error" />
<issue id="TypographyQuotes" severity="warning" />
<issue id="UnsafeProtectedBroadcastReceiver" severity="error" />
<issue id="UnprotectedSMSBroadcastReceiver" severity="error" />
<issue id="VectorRaster" severity="error" />
<issue id="ViewTag" severity="error" />
<issue id="WrongViewCast" severity="error" />
<issue id="Wakelock" severity="error" />
<issue id="WrongCall" severity="error" />
<issue id="WrongCase" severity="error" />
<issue id="InvalidId" severity="error" />
<issue id="NotSibling" severity="error" />
<issue id="UnknownIdInLayout" severity="error" />
<issue id="WrongFolder" severity="error" />
<issue id="AdapterViewChildren" severity="error" />
<issue id="SetJavaScriptEnabled" severity="warning" />
<issue id="AddJavascriptInterface" severity="warning" />
<issue id="AllowBackup" severity="error" />
<issue id="AlwaysShowAction" severity="error" />
<issue id="AppCompatMethod" severity="error" />
<issue id="Assert" severity="error" />
<issue id="CommitPrefEdits" severity="error" />
<issue id="CommitTransaction" severity="error" />
<issue id="CustomViewStyleable" severity="error" />
<issue id="CutPasteId" severity="error" />
<issue id="Deprecated" severity="error" />
<issue id="DeviceAdmin" severity="error" />
<issue id="DisableBaselineAlignment" severity="error" />
<issue id="DrawAllocation" severity="warning" />
<issue id="DuplicateIncludedIds" severity="error" />
<issue id="DuplicateUsesFeature" severity="error" />
<issue id="ExportedContentProvider" severity="error" />
<issue id="ExportedReceiver" severity="error" />
<issue id="ExportedService" severity="error" />
<issue id="ExtraText" severity="error" />
<issue id="GetInstance" severity="error" />
<issue id="GifUsage" severity="warning" />
<issue id="GradleDependency" severity="warning" />
<issue id="GradleDeprecated" severity="error" />
<issue id="GradleDynamicVersion" severity="error" />
<issue id="GradleOverrides" severity="error" />
<issue id="GradlePath" severity="warning" />
<issue id="GrantAllUris" severity="error" />
<issue id="HandlerLeak" severity="error" />
<issue id="HardcodedText" severity="error" />
<issue id="IconColors" severity="warning" />
<issue id="IconDensities" severity="warning" />
<issue id="IconDipSize" severity="error" />
<issue id="IconDuplicates" severity="error" />
<issue id="IconDuplicatesConfig" severity="error" />
<issue id="IconExtension" severity="error" />
<issue id="IconLocation" severity="error" />
<issue id="IconMixedNinePatch" severity="error" />
<issue id="IconNoDpi" severity="error" />
<issue id="IconXmlAndPng" severity="error" />
<issue id="IllegalResourceRef" severity="error" />
<issue id="InOrMmUsage" severity="error" />
<issue id="InconsistentArrays" severity="error" />
<issue id="InefficientWeight" severity="warning" />
<issue id="InflateParams" severity="warning" />
<issue id="InnerclassSeparator" severity="error" />
<issue id="LocaleFolder" severity="error" />
<issue id="LogConditional" severity="error" />
<issue id="ManifestOrder" severity="error" />
<issue id="MipmapIcons" severity="error" />
<issue id="MissingApplicationIcon" severity="error" />
<issue id="MissingId" severity="error" />
<issue id="MissingVersion" severity="error" />
<issue id="NestedWeights" severity="warning" />
<issue id="NewerVersionAvailable" severity="warning" />
<issue id="ObsoleteLayoutParam" severity="error" />
<issue id="OldTargetApi" severity="error" />
<issue id="PackageManagerGetSignatures" severity="error" />
<issue id="PrivateResource" severity="error" />
<issue id="ProguardSplit" severity="error" />
<issue id="PxUsage" severity="error" />
<issue id="Recycle" severity="error" />
<issue id="Registered" severity="error" />
<issue id="RtlEnabled" severity="error" />
<issue id="SQLiteString" severity="error" />
<issue id="ScrollViewCount" severity="error" />
<issue id="ScrollViewSize" severity="error" />
<issue id="SdCardPath" severity="error" />
<issue id="ShortAlarm" severity="warning" />
<issue id="ShowToast" severity="error" />
<issue id="SignatureOrSystemPermissions" severity="error" />
<issue id="SimpleDateFormat" severity="error" />
<issue id="SpUsage" severity="error" />
<issue id="StateListReachable" severity="error" />
<issue id="SuspiciousImport" severity="error" />
<issue id="TextFields" severity="error" />
<issue id="TextViewEdits" severity="error" />
<issue id="TooDeepLayout" severity="warning" />
<issue id="TooManyViews" severity="warning" />
<issue id="TypographyDashes" severity="warning" />
<issue id="UnknownIdInLayout" severity="error" />
<issue id="UnlocalizedSms" severity="error" />
<issue id="UnusedIds" severity="error" />
<issue id="UseAlpha2" severity="error" />
<issue id="UseSparseArrays" severity="warning" />
<issue id="UseValueOf" severity="error" />
<issue id="UsesMinSdkAttributes" severity="error" />
<issue id="UsingHttp" severity="warning" />
<issue id="WorldReadableFiles" severity="warning" />
<issue id="WorldWriteableFiles" severity="warning" />
<issue id="WrongRegion" severity="warning" />
<!-- DISABLE RULES BELOW -->
<issue id="GoogleAppIndexingApiWarning" severity="ignore" />
<issue id="GoogleAppIndexingWarning" severity="ignore" />
<issue id="AppLinksAutoVerifyError" severity="ignore" />
<issue id="AppLinksAutoVerifyWarning" severity="ignore" />
<issue id="ClickableViewAccessibility" severity="ignore" />
<issue id="SetTextI18n" severity="ignore" />
<issue id="EasterEgg" severity="ignore" />
<issue id="BackButton" severity="ignore" />
<issue id="FieldGetter" severity="ignore" />
<issue id="Orientation" severity="ignore" />
<issue id="Suspicious0dp" severity="ignore" />
<issue id="NegativeMargin" severity="ignore" />
<issue id="MissingQuantity" severity="ignore" />
<issue id="ImpliedQuantity" severity="ignore" />
<issue id="TrulyRandom" severity="ignore" />
<issue id="SelectableText" severity="ignore" />
<issue id="UnusedIds" severity="ignore" />
<issue id="ButtonCase" severity="ignore" />
<issue id="ButtonOrder" severity="ignore" />
<issue id="ButtonStyle" severity="ignore" />
<issue id="ContentDescription" severity="ignore" />
<issue id="ExportedPreferenceActivity" severity="ignore" />
<!-- remove when facebook will update support library in sdk -->
<issue id="GradleCompatible" severity="ignore" />
<issue id="IconLauncherShape" severity="ignore" />
<issue id="IconMissingDensityFolder" severity="ignore" />
<issue id="InconsistentLayout" severity="ignore" />
<issue id="LabelFor" severity="ignore" />
<issue id="MissingTranslation" severity="ignore" />
<issue id="NestedScrolling" severity="ignore" />
<issue id="MergeRootFrame" severity="ignore" />
<issue id="NewApi" severity="ignore" />
<issue id="Overdraw" severity="ignore" />
<issue id="PluralsCandidate" severity="ignore" />
<issue id="RelativeOverlap" severity="ignore" />
<issue id="RtlHardcoded" severity="ignore" />
<issue id="RtlSymmetry" severity="ignore" />
<issue id="SmallSp" severity="ignore" />
<issue id="StringFormatCount" severity="ignore" />
<issue id="TypographyFractions" severity="ignore" />
<issue id="Typos" severity="ignore" />
<issue id="UnusedQuantity" severity="ignore" />
<issue id="UnusedResources" severity="ignore" />
<issue id="UseCompoundDrawables" severity="ignore" />
<issue id="UselessLeaf" severity="ignore" />
<issue id="UselessParent" severity="ignore" />
<issue id="ViewConstructor" severity="ignore" />
<issue id="ViewHolder" severity="ignore" />
<issue id="WebViewLayout" severity="ignore" />
<issue id="TypographyEllipsis" severity="ignore" />
<issue id="PermissionImpliesUnsupportedChromeOsHardware" severity="ignore" />
</lint>

View File

@ -32,6 +32,8 @@ opt_in_rules:
- fatal_error_message
- extension_access_modifier
- explicit_init
- prefer_zero_over_explicit_init
- fallthrough
# style
@ -64,6 +66,7 @@ opt_in_rules:
- identical_operands
- overridden_super_call
- unowned_variable_capture
- comment_spacing
# metrics
@ -73,7 +76,8 @@ excluded:
- Carthage
- Pods
- Generated
- Localization
- "**/Generated"
- "**/Resources"
line_length:
warning: 128
@ -111,6 +115,8 @@ identifier_name:
warning_threshold: 1
allow_zero_lintable_files: true
custom_rules:
# General
@ -186,26 +192,12 @@ custom_rules:
regex: '(?!\n)[^ \n]+ {2,}.+'
message: "Remove excess empty spaces"
severity: warning
match_kinds:
- argument
- attribute.builtin
- attribute.id
- buildconfig.id
- buildconfig.keyword
- identifier
- keyword
- number
- objectliteral
- parameter
- placeholder
# - string # all except string literals
# - comment # and comments
# - comment.mark
# - comment.url
# - doccomment
# - doccomment.field
- string_interpolation_anchor
- typeidentifier
excluded_match_kinds:
- comment
- comment.mark
- comment.url
- doccomment
- doccomment.field
getter_setter_style:
name: "Wrong getter/setter code style"
@ -213,43 +205,67 @@ custom_rules:
match_kinds:
- keyword
message: "Make a new line break when use getter or setter"
severity: error
severity: warning
redundant_boolean_condition:
name: "Redundant Boolean Condition"
regex: "(== true)|(== false)|(!= true)|(!= false)"
message: "Comparing a boolean to true is redundant (use `?? false` for optionals), and `!`-syntax is preferred over comparing to false."
severity: error
severity: warning
excluded_match_kinds:
- comment
- comment.mark
- comment.url
- doccomment
- doccomment.field
redundant_ternary_operator:
name: "Redundant Ternary Operator"
regex: "(\\? true \\: false)|(\\? false \\: true)"
message: "Returning a boolean as true is redundant, and `!`-syntax is preferred over returning as false."
severity: error
severity: warning
single_line_closure:
name: "Single line closure"
regex: '\{([^\n\/]*\[[^\]]+\][^\n\/]*)?([^\n\/]*[a-zA-Z]\w*(, \w+)*)? in [^\n\/]+'
message: "Too complex expression for single line closure. Improve readability by making it multiline."
severity: error
severity: warning
addSubview_in_cell:
name: "Usage addSubview in cell"
regex: '(extension|class)\s*\w+Cell(:| )(?s).*(self\.|\s{2,})add(Subv|V)iews?\(\w'
message: "Use сontentView instead of self for addSubview or addSubviews methods in cell."
severity: error
severity: warning
redundant_type_annotation_bool:
name: "Redundant type annotation for Bool"
regex: '((var|let)) *\w+ *((: *Bool *=)|((\w| |<|>|:)*= *BehaviorRelay<Bool>\( *value *:)) *((true)|(false))'
regex: '\s((var|let))\s{1,}\w+ *((: *Bool *=)|((\w| |<|>|:)*= *BehaviorRelay<Bool>\( *value *:)) *((true)|(false))'
message: "Using a type annotation for Bool is redundant."
severity: error
severity: warning
parameter_repetition:
name: "Parameter repetition"
regex: 'func ((\w+([A-Z]\w+))|(\w+)) *(<[^>]+>)? *\( *(?i)(\3|\4):'
message: "The parameter name is actually used in the function name. Use _ instead."
severity: error
severity: warning
parameter_closure:
name: "Parameter closure"
regex: '\w*Closure<[^\r\n\t\f\v]*, Void[^\r\n\t\f\v]*>'
message: "Use `ParameterClosure` instead of declaring an explicit return value of `Void`."
severity: warning
strong_self:
name: "Strong self"
regex: '(if|guard)\s+let\s+self\s+=\s+self'
message: "Use a local function instead of capture strong self"
severity: warning
pattern_matching:
name: "Pattern matching"
regex: 'case[^\n\(]+\([^\)]*(let|var)\s'
message: "Use a let|var keyword behind parentheses"
severity: warning
# Rx
@ -259,6 +275,12 @@ custom_rules:
message: "Replace Rx.map operator with replace(with:) or asVoid(). For Sequence.map consider using forEach."
severity: warning
disposable_nil:
name: "Disposable nil"
regex: ' *\S*(d|D)isposable\?? *= *nil'
message: "nil assigning doesn't dispose subscription. Call `dispose()` instead."
severity: error
# LeadKit
multiple_add_subview:

View File

@ -3,8 +3,8 @@ file_link=$2
folder=$3
flag_of_delete=$4
readonly key_of_delete="--remove-cached"
readonly default_folder="./Downloads"
key_of_delete="--remove-cached"
default_folder="./Downloads"
if [[ ${folder} = ${key_of_delete} ]]; then
folder="${default_folder}"

View File

@ -1,14 +1,13 @@
<?php
$PRODUCT_NAME = $argv[1];
$LOCALIZATION_PATH = $argv[1];
$COMMON_STRINGS_PATH = $argv[2];
$BUNDLE = $argv[3];
function createFolder($path) {
if (!file_exists($path)) {
mkdir($path, 0777, true);
}
}
$localization = './'.$PRODUCT_NAME.'/Resources/Localization/';
$baseFile = file_get_contents(array_pop(glob($COMMON_STRINGS_PATH.'/default*.json')));
$baseJson = json_decode($baseFile, true);
@ -31,25 +30,26 @@
}
$ios_strings = preg_replace('/(\\\\)(u)([0-9a-fA-F]{4})/', '$1U$3', $ios_strings);
$lproj = $localization.$languageName.'.lproj/';
$lproj = $LOCALIZATION_PATH.$languageName.'.lproj/';
createFolder($lproj);
file_put_contents($lproj.'Localizable.strings', $ios_strings);
if($isBase) {
createFolder($localization.'Base.lproj/');
file_put_contents($localization.'Base.lproj/Localizable.strings', $ios_strings);
createFolder($LOCALIZATION_PATH.'Base.lproj/');
file_put_contents($LOCALIZATION_PATH.'Base.lproj/Localizable.strings', $ios_strings);
$ios_swift_strings = 'import Foundation'.PHP_EOL.PHP_EOL.
'// swiftlint:disable superfluous_disable_command'.PHP_EOL.
'// swiftlint:disable line_length'.PHP_EOL.
'// swiftlint:disable file_length'.PHP_EOL.
'// swiftlint:disable cyrillic_strings'.PHP_EOL.
'// swiftlint:disable identifier_name'.PHP_EOL.PHP_EOL.
'public extension String {'.PHP_EOL;
foreach ($json as $key=>$value) {
$value_without_linefeed = preg_replace("/\r|\n/", " ", $value);
$ios_swift_strings .= "\t/// ".$value_without_linefeed."\n\t".'static let '.preg_replace_callback('/_(.?)/', function ($m) { return strtoupper($m[1]); }, $key).' = NSLocalizedString("'.$key.'", comment: "")'."\n".PHP_EOL;
$ios_swift_strings .= "\t/// ".$value_without_linefeed."\n\t".'static let '.preg_replace_callback('/_(.?)/', function ($m) { return strtoupper($m[1]); }, $key).' = NSLocalizedString("'.$key.'", bundle: '.$BUNDLE.', comment: "'.addslashes($value_without_linefeed).'")'."\n".PHP_EOL;
}
$ios_swift_strings .= '}'.PHP_EOL;
file_put_contents($localization.'String+Localization.swift', $ios_swift_strings);
file_put_contents($LOCALIZATION_PATH.'String+Localization.swift', $ios_swift_strings);
}
}
?>

View File

@ -0,0 +1,39 @@
#!/bin/sh
# Description:
# Add user defined enviroment if programm not found
#
# Parameters:
# $1 - programm
#
# Examples of usage:
# . install_env.sh pmd
#
# When you run Git from the command line, it runs in the environment as set up by your Shell.
# GUI OS X apps, however, have no knowledge about your shell - and the PATH environment can be changed in many different places.
# Export our profile with path by ourselves
function source_home_file {
file="$HOME/$1"
if [[ -f "${file}" ]]; then
if ! source "${file}"; then
export_commands="$(cat "${file}" | grep "^export PATH=")"
while read export_command
do
eval "$export_command"
done <<< "$export_commands"
fi
fi
return 1
}
# Use specific exec due to Xcode has custom value of $PATH
if [ -z "$(which $1)" ]; then
source_home_file ".bash_profile" || source_home_file ".zshrc" || source_home_file ".zprofile" || true
echo "User defined enviroment has been set for ${1}"
fi

14
xcode/bootstrap/Brewfile Normal file
View File

@ -0,0 +1,14 @@
# Working environment
brew "rbenv" # ruby + bundler
brew "gettext"
# Code, configs and project generation
brew "php"
brew "python"
brew "xcodegen"
# code quality
brew "pmd"
# CI badge
# brew "imagemagick"

9
xcode/bootstrap/Gemfile Normal file
View File

@ -0,0 +1,9 @@
source "https://rubygems.org"
gem "cocoapods"
gem "fastlane"
gem 'mustache' # for config generator
gem 'xcode-install'
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)

86
xcode/bootstrap/Makefile Normal file
View File

@ -0,0 +1,86 @@
GREEN := $(shell tput -Txterm setaf 2)
YELLOW := $(shell tput -Txterm setaf 3)
WHITE := $(shell tput -Txterm setaf 7)
RESET := $(shell tput -Txterm sgr0)
RUBY_VERSION="2.7.6"
open_project=(open *.xcworkspace)
install_dev_certs=(bundle exec fastlane SyncCodeSigning type:development readonly:true)
install_pods=(bundle exec pod install || bundle exec pod install --repo-update)
TARGET_MAX_CHAR_NUM=20
## Show help
help:
@echo ''
@echo 'Использование:'
@echo ' ${YELLOW}make${RESET} ${GREEN}<target>${RESET}'
@echo ''
@echo 'Команды:'
@awk '/^[a-zA-Z\-\_0-9]+:/ { \
helpMessage = match(lastLine, /^## (.*)/); \
if (helpMessage) { \
helpCommand = substr($$1, 0, index($$1, ":")-1); \
helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \
printf " ${YELLOW}%-$(TARGET_MAX_CHAR_NUM)s${RESET} ${GREEN}%s${RESET}\n", helpCommand, helpMessage; \
} \
} \
{ lastLine = $$0 }' $(MAKEFILE_LIST)
## Инициализирует проект и устанавливает системные утилиты
init:
brew bundle
eval "$(rbenv init -)"
rbenv install -s ${RUBY_VERSION}
rbenv global ${RUBY_VERSION}
if ! gem spec bundler > /dev/null 2>&1; then\
echo "bundler gem is not installed!";\
-sudo gem install bundler;\
fi
bundle install
xcodegen
$(call install_pods)
bundle exec fastlane install_plugins
$(call install_dev_certs)
$(call open_project)
git config --local core.hooksPath .githooks
## Устанавливает поды
pod:
$(call install_pods)
## Устанавливает сертификат и профили для запуска на девайсе
dev_certs:
$(call install_dev_certs)
## Открывает папку для ручного редактирования сертификатов и профайлов
update_certs:
bundle exec fastlane ManuallyUpdateCodeSigning
## Поднимает версию приложения (параметр "X.Y.Z")
bumpAppVersion:
ifeq ($(version),undefined)
@echo "Version parameter is missing (ex: x.y.z)" $(target)
else
bundle exec fastlane run increment_version_number version_number:$(version)
endif
## Позволяет быстро открыть workspace проекта
start:
$(call open_project)
## Очищает содержимое папки DerivedData
clean:
rm -rf ~/Library/Developer/Xcode/DerivedData/*

6
xcode/bootstrap/setup.command Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PROJECT_DIR="${DIR}/../../../"
make init -C ${PROJECT_DIR}

View File

@ -1,9 +1,295 @@
VERSION=$1
FILE_NAME="api-generator-${VERSION}.jar"
#!/bin/sh
# download api generator
link="https://maven.dev.touchin.ru/ru/touchin/api-generator/${VERSION}/${FILE_NAME}"
. build-scripts/xcode/aux_scripts/download_file.sh ${FILE_NAME} ${link}
# Description:
# Generates API models & methods.
#
# Parameters:
# $1 - api generator version.
# $2 - path to generated code directory
#
# Required environment variables:
# SRCROOT - path to project folder.
#
# Optional environment variables:
# OUTPUT_PATH - path to Generated folder.
# API_SPEC_DIR - path to api specification folder
# VERBOSE - print debug messages
# API_NAME - project name that will be used by generator (example: OUTPUT_PATH/API_NAME/Classes )
#
# Examples of usage:
# . api_generator.sh 1.4.0-beta1
# . api_generator.sh 1.4.0-beta1 ${TARGET_NAME}/Generated
#
# execute api generator
java -Xmx6g -jar "Downloads/${FILE_NAME}" generate-client-code --output-language SWIFT --specification-path common/api --output-path ${PRODUCT_NAME}/Generated --single-file true
readonly EXIT_SUCCESS=0
readonly EXIT_FAILURE=1
readonly TRUE=0
readonly FALSE=1
readonly LOG_TAG="API-GENERATOR"
notice()
{
echo "${LOG_TAG}:NOTICE: ${1}"
}
debug()
{
if [ ! -z "${VERBOSE}" ]; then
echo "${LOG_TAG}:DEBUG: ${1}"
fi
}
is_force_run()
{
if [ -z "${FORCE_RUN}" ]; then
return ${FALSE}
fi
local -r STR_MODE=`tr "[:upper:]" "[:lower:]" <<< ${FORCE_RUN}`
if [ ${STR_MODE} == "yes" ] || [ ${STR_MODE} == "true" ] || [ ${STR_MODE} == "1" ]; then
return ${TRUE}
fi
return ${FALSE}
}
is_single_file()
{
if [ -z "${SINGLE_FILE}" ]; then
echo "true"
return
fi
local -r STR_MODE=`tr "[:upper:]" "[:lower:]" <<< ${SINGLE_FILE}`
if [ ${STR_MODE} == "no" ] || [ ${STR_MODE} == "false" ] || [ ${STR_MODE} == "0" ]; then
echo "false"
else
echo "true"
fi
}
get_api_spec_current_commit()
{
if [ -z "${API_SPEC_DIR}" ]; then
if [ ! -z "${1}" ]; then
echo `git -C ${1} rev-parse --verify HEAD`
else
echo `git rev-parse --verify HEAD`
fi
else
echo `git -C ${API_SPEC_DIR} rev-parse --verify HEAD`
fi
}
is_api_spec_under_source_control()
{
local IS_UNDER_SOURCE_CONTROL_CHECK
if [ -z "${API_SPEC_DIR}" ]; then
if [ ! -z "${1}" ]; then
IS_UNDER_SOURCE_CONTROL_CHECK=`git -C ${1} rev-parse --is-inside-work-tree 2>/dev/null`
else
IS_UNDER_SOURCE_CONTROL_CHECK=`git rev-parse --is-inside-work-tree 2>/dev/null`
fi
else
IS_UNDER_SOURCE_CONTROL_CHECK=`git -C ${API_SPEC_DIR} rev-parse --is-inside-work-tree 2>/dev/null`
fi
[ ${IS_UNDER_SOURCE_CONTROL_CHECK} = "true" ]
}
is_nothing_changed_since_last_check()
{
if is_force_run; then
notice "Force run detected. Skipping commits comparison."
return ${EXIT_FAILURE}
fi
if [ -z "${COMMIT_FILE_PATH}" ]; then
if [ ! -z "${1}" ]; then
local -r COMMIT_FILE_PATH=${1}
else
debug "COMMIT_FILE_PATH should be defined or passed as first argument!"
return ${EXIT_FAILURE}
fi
fi
if is_api_spec_under_source_control; then
local -r CURRENT_COMMIT=`get_api_spec_current_commit`
local -r LAST_CHECKED_COMMIT=`cat ${COMMIT_FILE_PATH} 2> /dev/null || echo ""`
if [ ${CURRENT_COMMIT} = "${LAST_CHECKED_COMMIT}" ]; then
return ${EXIT_SUCCESS}
else
return ${EXIT_FAILURE}
fi
else
return ${EXIT_SUCCESS}
fi
}
record_current_commit()
{
if is_force_run; then
notice "Force run detected. Commit won't be recorder."
exit ${EXIT_SUCCESS}
fi
if [ -z "${COMMIT_FILE_PATH}" ]; then
if [ ! -v "${1}" ]; then
local -r COMMIT_FILE_PATH=${1}
else
debug "COMMIT_FILE_PATH should be defined or passed as second argument!"
return ${EXIT_FAILURE}
fi
fi
local -r CURRENT_COMMIT=`get_api_spec_current_commit`
echo ${CURRENT_COMMIT} > ${COMMIT_FILE_PATH}
}
openapi_codegen()
{
if [ -z "${OPEN_API_SPEC_PATH}" ]; then
if [ ! -v "${1}" ]; then
local -r OPEN_API_SPEC_PATH=${1}
else
debug "OPEN_API_SPEC_PATH should be defined or passed as first argument!"
return ${EXIT_FAILURE}
fi
fi
if [ -z "${OUTPUT_PATH}" ]; then
if [ ! -v "${2}" ]; then
local -r OUTPUT_PATH=${2}
else
debug "OUTPUT_PATH should be defined or passed as second argument!"
return ${EXIT_FAILURE}
fi
fi
if [ -z "${VERSION}" ]; then
if [ ! -v "${3}" ]; then
local -r VERSION=${3}
else
debug "VERSION should be defined or passed as third argument!"
return ${EXIT_FAILURE}
fi
fi
if [ -z "${API_NAME}" ]; then
local -r API_NAME="${PROJECT_NAME}API"
fi
notice "OpenAPI spec generation for ${OPEN_API_SPEC_PATH}"
local -r CODEGEN_VERSION="3.0.34"
local -r CODEGEN_FILE_NAME="swagger-codegen-cli-${CODEGEN_VERSION}.jar"
local -r CODEGEN_DOWNLOAD_URL="https://repo1.maven.org/maven2/io/swagger/codegen/v3/swagger-codegen-cli/${CODEGEN_VERSION}/${CODEGEN_FILE_NAME}"
. build-scripts/xcode/aux_scripts/download_file.sh ${CODEGEN_FILE_NAME} ${CODEGEN_DOWNLOAD_URL}
local -r TINETWORKING_CODEGEN_FILE_NAME="codegen-${VERSION}.jar"
local -r DOWNLOAD_URL="https://maven.dev.touchin.ru/ru/touchin/codegen/${VERSION}/${TINETWORKING_CODEGEN_FILE_NAME}"
. build-scripts/xcode/aux_scripts/download_file.sh ${TINETWORKING_CODEGEN_FILE_NAME} ${DOWNLOAD_URL}
rm -rf ${OUTPUT_PATH}/${API_NAME} # remove previously generated API (if exists)
java -cp "Downloads/${CODEGEN_FILE_NAME}:Downloads/${TINETWORKING_CODEGEN_FILE_NAME}" io.swagger.codegen.v3.cli.SwaggerCodegen generate -l TINetworking -i ${OPEN_API_SPEC_PATH} -o ${OUTPUT_PATH} --additional-properties projectName=${API_NAME}
# flatten folders hierarchy
mv ${OUTPUT_PATH}/${API_NAME}/Classes/Swaggers/* ${OUTPUT_PATH}/${API_NAME}/
rm -rf ${OUTPUT_PATH}/${API_NAME}/Classes
}
api_generator_codegen()
{
if [ -z "${API_SPEC_DIR}" ]; then
if [ ! -v "${1}" ]; then
local -r API_SPEC_DIR=${1}
else
debug "API_SPEC_DIR should be defined or passed as first argument!"
return ${EXIT_FAILURE}
fi
fi
if [ -z "${OUTPUT_PATH}" ]; then
if [ ! -v "${2}" ]; then
local -r OUTPUT_PATH=${2}
else
debug "OUTPUT_PATH should be defined or passed as second argument!"
return ${EXIT_FAILURE}
fi
fi
if [ -z "${VERSION}" ]; then
if [ ! -v "${3}" ]; then
local -r VERSION=${3}
else
debug "VERSION should be defined or passed as third argument!"
return ${EXIT_FAILURE}
fi
fi
notice "api-generator spec generation for ${API_SPEC_DIR}/main.json"
local -r FILE_NAME="api-generator-${VERSION}.jar"
local -r DOWNLOAD_URL="https://maven.dev.touchin.ru/ru/touchin/api-generator/${VERSION}/${FILE_NAME}"
. build-scripts/xcode/aux_scripts/download_file.sh ${FILE_NAME} ${DOWNLOAD_URL}
java -Xmx12g -jar "Downloads/${FILE_NAME}" generate-client-code --output-language SWIFT --specification-path ${API_SPEC_DIR} --output-path ${OUTPUT_PATH} --single-file $(is_single_file)
}
readonly BUILD_PHASES_DIR=${SRCROOT}/build_phases
mkdir -p ${BUILD_PHASES_DIR}
readonly COMMIT_FILE_PATH=${BUILD_PHASES_DIR}/api-generator-commit
if is_nothing_changed_since_last_check; then
notice "Nothing was changed. API generation skipped."
exit ${EXIT_SUCCESS}
fi
readonly VERSION=$1
if [ -z "${OUTPUT_PATH}" ]; then
if [ ! -z "${2}" ]; then
readonly OUTPUT_PATH=${2}
else
readonly OUTPUT_PATH="Generated"
fi
fi
if [ -z "${API_SPEC_DIR}" ]; then
readonly API_SPEC_DIR="common/api"
fi
mkdir -p ${OUTPUT_PATH}
readonly OPEN_API_SPEC_PATH=`find ${API_SPEC_DIR} -maxdepth 1 -name '*.yaml' -o -name '*.yml' | head -n 1`
if [ -f "${OPEN_API_SPEC_PATH}" ]; then
openapi_codegen
elif [ -f "${API_SPEC_DIR}/main.json" ]; then
api_generator_codegen
else
notice "No api spec found!"
exit ${EXIT_FAILURE}
fi
if [ $? -ne ${EXIT_FAILURE} ]; then
record_current_commit
fi

Binary file not shown.

View File

@ -0,0 +1,93 @@
#!/bin/sh
# Description:
# Converts SCRIPT_INPUT_FILE_{N} or SCRIPT_INPUT_FILE_LIST_{N} variables to string that contains
# list of file names splitted by given separator.
#
# Parameters:
# $1 - separator to use.
# $2 - default value to return if SCRIPT_INPUT_FILE_COUNT or SCRIPT_INPUT_FILE_LIST_COUNT is zero.
#
# Optional environment variables:
# FILE_NAMES_SEPARATOR - separator to use.
# DEFAULT_FILE_NAMES - default value if was found in environment variables.
# SCRIPT_INPUT_FILE_COUNT - number of files listed in "Input files" section of build phase.
# SCRIPT_INPUT_FILE_{N} - file path of specific input file at index.
# SCRIPT_INPUT_FILE_LIST_COUNT - number of files listed in "Input File Lists" section of build phase.
# SCRIPT_INPUT_FILE_LIST_{N} - file path to specifis xcfilelist file at index.
#
# Examples of usage:
# read_input_file_names
# read_input_file_names.sh " " path/to/project
#
has_input_files()
{
[ ! -z "${SCRIPT_INPUT_FILE_COUNT}" ] && [ ${SCRIPT_INPUT_FILE_COUNT} -gt 0 ]
}
has_input_file_lists()
{
[ ! -z "${SCRIPT_INPUT_FILE_LIST_COUNT}" ] && [ ${SCRIPT_INPUT_FILE_LIST_COUNT} -gt 0 ]
}
if [ -z "${FILE_NAMES_SEPARATOR}" ]; then
if [ ! -z "${1}" ]; then
FILE_NAMES_SEPARATOR=${1}
else
FILE_NAMES_SEPARATOR=" "
fi
fi
if [ -z "${DEFAULT_FILE_NAMES}" ]; then
if [ ! -z "${2}" ]; then
DEFAULT_FILE_NAMES=${2}
else
DEFAULT_FILE_NAMES=""
fi
fi
INPUT_FILE_NAMES=""
if has_input_files && has_input_file_lists; then
>&2 echo "Passing Input Files and Input Files Lists is not supported!\nOnly Input Files will be used."
fi
if has_input_files && \
[ ${SCRIPT_INPUT_FILE_COUNT} -gt 0 ]; then
for i in `seq 0 $((${SCRIPT_INPUT_FILE_COUNT}-1))`
do
SCRIPT_INPUT_FILE_VARIABLE_NAME="SCRIPT_INPUT_FILE_${i}"
SHELL_VARIABLE="\${${SCRIPT_INPUT_FILE_VARIABLE_NAME}}"
RESOLVED_FILE_NAME=`envsubst <<< ${SHELL_VARIABLE}`
INPUT_FILE_NAMES=${INPUT_FILE_NAMES}${FILE_NAMES_SEPARATOR}${RESOLVED_FILE_NAME}
done
FILE_NAMES_SEPARATOR_LENGTH=`awk '{ print length; }' <<< "${FILE_NAMES_SEPARATOR}"`
if [ ${FILE_NAMES_SEPARATOR_LENGTH} -gt 0 ] && \
[ ! -z "${INPUT_FILE_NAMES}" ]; then
# remove separator prefix
INPUT_FILE_NAMES=`cut -c${FILE_NAMES_SEPARATOR_LENGTH}- <<< ${INPUT_FILE_NAMES}`
fi
elif has_input_file_lists; then
for i in `seq 0 $((${SCRIPT_INPUT_FILE_LIST_COUNT}-1))`
do
SCRIPT_INPUT_FILE_LIST_VARIABLE_NAME="SCRIPT_INPUT_FILE_LIST_${i}"
SHELL_VARIABLE="\${${SCRIPT_INPUT_FILE_LIST_VARIABLE_NAME}}"
FILE_NAME=`envsubst <<< ${SHELL_VARIABLE}`
RESOLVED_FILE_NAMES=`envsubst < ${FILE_NAME}`
for INPUT_FILE_NAME in ${RESOLVED_FILE_NAMES}; do
INPUT_FILE_NAMES=${INPUT_FILE_NAMES}${INPUT_FILE_NAME}${FILE_NAMES_SEPARATOR}
done
done
fi
if [ -z "${INPUT_FILE_NAMES}" ]; then
echo ${DEFAULT_FILE_NAMES}
else
echo ${INPUT_FILE_NAMES}
fi

Binary file not shown.

61
xcode/build_phases/copy_paste_detection.sh Normal file → Executable file
View File

@ -1,20 +1,61 @@
#!/bin/sh
# Description:
# Validates code for copy-paste, prints results to standard output and report file.
#
# Parameters:
# $1 $2 $3 $n - folders to exclude from code checking.
#
# Required environment variables:
# PROJECT_DIR - project directory.
# SCRIPT_DIR - directory of current script.
#
# Optional environment variables:
# SCRIPT_INPUT_FILE_COUNT - number of files listed in "Input files" of build phase.
# SCRIPT_INPUT_FILE_{N} - file path to directory that should be checked.
#
# Modified files:
# ${PROJECT_DIR}/code-quality-reports/CPDLog.txt - check report.
#
# Example of usage:
# runner.sh copy_paste_detection.sh Generated Localization Pods
#
readonly EXIT_SUCCESS=0
readonly EXIT_FAILURE=1
. ${SCRIPT_DIR}/../aux_scripts/install_env.sh pmd
if which pmd >/dev/null; then
# running CPD
readonly SOURCES_DIR=${1:-${PROJECT_DIR}} # first argument or PROJECT_DIR
readonly REPORTS_DIR=${PROJECT_DIR}/code-quality-reports
readonly FILES_TO_EXCLUDE=`find ${SOURCES_DIR} -type d -name Localization -or -name Generated -or -name Carthage -or -name Pods | paste -sd " " -`
readonly REPORTS_DIR="${PROJECT_DIR}/code-quality-reports"
mkdir ${REPORTS_DIR}
readonly SOURCES_DIRS=`. ${SCRIPT_DIR}/common/read_input_file_names.sh " " ${PROJECT_DIR}`
pmd cpd --files ${SOURCES_DIR} --exclude ${FILES_TO_EXCLUDE} --minimum-tokens 50 --language swift --encoding UTF-8 --format net.sourceforge.pmd.cpd.XMLRenderer > ${REPORTS_DIR}/cpd-output.xml --failOnViolation true
readonly COMMAND_LINE_ARGUMENTS=$@
php ./build-scripts/xcode/aux_scripts/cpd_script.php ${REPORTS_DIR}/cpd-output.xml | tee ${REPORTS_DIR}/CPDLog.txt
FOLDERS_TO_EXLUDE=""
# Make paths relative to SOURCES_DIR, so different developers won't rewrite entire file
readonly SED_REPLACEMENT_STRING=$(echo ${SOURCES_DIR} | sed "s/\//\\\\\//g")
for argument in ${COMMAND_LINE_ARGUMENTS}
do
FOLDERS_TO_EXLUDE=${FOLDERS_TO_EXLUDE}"-or -name ${argument} "
done
FOLDERS_TO_EXLUDE=`echo ${FOLDERS_TO_EXLUDE} | cut -c5-` # remove first "-or"
readonly FILES_TO_EXCLUDE=`find ${PROJECT_DIR} -type d ${FOLDERS_TO_EXLUDE} | paste -sd " " -`
mkdir -p ${REPORTS_DIR}
pmd cpd --files ${SOURCES_DIRS} --exclude ${FILES_TO_EXCLUDE} --minimum-tokens 50 --language swift --encoding UTF-8 --format net.sourceforge.pmd.cpd.XMLRenderer --failOnViolation true > ${REPORTS_DIR}/cpd-output.xml
php ${SCRIPT_DIR}/../aux_scripts/cpd_script.php ${REPORTS_DIR}/cpd-output.xml | tee ${REPORTS_DIR}/CPDLog.txt
# Make paths relative to PROJECT_DIR, so different developers won't rewrite entire file
readonly SED_REPLACEMENT_STRING=$(echo ${PROJECT_DIR} | sed "s/\//\\\\\//g")
sed -i '' "s/${SED_REPLACEMENT_STRING}//g" "${REPORTS_DIR}/CPDLog.txt"
else
echo "warning: pmd not installed, install using 'brew install pmd'"
exit 1
exit ${EXIT_FAILURE}
fi

View File

@ -1 +1,2 @@
. build-scripts/xcode/aux_scripts/certificates_readme_generator.sh > $PROJECT_DIR/Certificates/README.md
readonly BUILD_SCRIPTS_DIR=${1:-${PROJECT_DIR}} # first argument or PROJECT_DIR
. $BUILD_SCRIPTS_DIR/build-scripts/xcode/aux_scripts/certificates_readme_generator.sh > $PROJECT_DIR/Certificates/README.md

View File

@ -0,0 +1,22 @@
require 'yaml'
require_relative '../../managers/managers'
require_relative '../../templates/templates'
# Input files paths
build_settings_file_path = ARGV[0]
generated_features_enum_file_path = ARGV[1]
build_settings_features_list = Managers::FileManager.load_from_file_YAML(build_settings_file_path)["features"]
if build_settings_features_list.nil? or build_settings_features_list.empty?
raise "There are no features in " + build_settings_file_path
end
# Generate enum Feature Toggles
features_enum_template = Templates::FeatureTemplates.features_enum
utils = Managers::TemplateManager.new(build_settings_features_list)
rendered_enum = utils.render(features_enum_template).strip
Managers::FileManager.save_data_to_file(generated_features_enum_file_path, rendered_enum)

View File

@ -0,0 +1,19 @@
# Input paths
readonly BUILD_SETTINGS_FILE_PATH=${1:-${PROJECT_DIR}/common/build_settings.yaml}
readonly FEATURES_ENUM_FILE_PATH=${2:-${PROJECT_DIR}/${PRODUCT_NAME}/Resources/Features/Feature.swift}
# Features enunm generator script
readonly CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
readonly GENERATOR_SCRIPT=${CURRENT_DIR}/features_generator.rb
if ! [ -e ${BUILD_SETTINGS_FILE_PATH} ]; then
echo "File ${BUILD_SETTINGS_FILE_PATH} does not exist. Add this file and try again."
exit 1
fi
if ! [ -e ${FEATURES_ENUM_FILE_PATH} ]; then
echo "File ${FEATURES_ENUM_FILE_PATH} does not exist. Add this file and try again."
exit 1
fi
ruby ${GENERATOR_SCRIPT} ${BUILD_SETTINGS_FILE_PATH} ${FEATURES_ENUM_FILE_PATH}

View File

@ -1,16 +1,47 @@
LOCALIZATION_PATH="${PRODUCT_NAME}/Resources/Localization"
#first argument set strings folder path
#!/bin/sh
# Description:
# Generates Localizeable.strings and String+Localization.swift files.
#
# Parameters:
# $1 - path to strings folder containing json files.
# $2 - path to Localization folder (output).
# $3 - Bundle for localization. Default is `.main`.
#
# Required environment variables:
# SCRIPT_DIR - directory of current script.
#
# Optional environment variables:
# PRODUCT_NAME - product name to produce path to localization folder (output).
#
# Examples of usage:
# . localization.sh
# . localization.sh common/strings Resources/Localization/ .main
#
readonly EXIT_SUCCESS=0
readonly EXIT_FAILURE=1
. ${SCRIPT_DIR}/../aux_scripts/install_env.sh php
STRINGS_FOLDER=${1:-"common/strings"}
LOCALIZATION_PATH=${2:-"${PRODUCT_NAME}/Resources/Localization/"}
BUNDLE=${3:-".main"}
if ! [ -e ${LOCALIZATION_PATH} ]; then
echo "${PROJECT_DIR}/${LOCALIZATION_PATH} path does not exist. Add these folders and try again."
exit 1
echo "${LOCALIZATION_PATH} path does not exist. Add these folders and try again."
exit ${EXIT_FAILURE}
fi
if ! [ -e "${PROJECT_DIR}/${STRINGS_FOLDER}" ]; then
echo "${PROJECT_DIR}/${STRINGS_FOLDER} path does not exist. Submodule with strings should be named common and contain strings folder."
exit 1
if ! [ -e "${STRINGS_FOLDER}" ]; then
echo "${STRINGS_FOLDER} path does not exist. Submodule with strings should be named common and contain strings folder."
exit ${EXIT_FAILURE}
fi
#second argument set strings script path
php ${2:-build-scripts/xcode/aux_scripts/import_strings.php} ${PRODUCT_NAME} ${STRINGS_FOLDER}
if which php >/dev/null; then
php ${SCRIPT_DIR}/../aux_scripts/import_strings.php ${LOCALIZATION_PATH} ${STRINGS_FOLDER} ${BUNDLE}
else
echo "warning: php not installed, install using 'brew install php'"
exit ${EXIT_FAILURE}
fi

View File

@ -0,0 +1,9 @@
# source: https://github.com/iKenndac/verify-string-files
# first argument set base localization strings path
readonly LOCALIZATION_PATH=${1:-${PRODUCT_NAME}/Resources/Localization/Base.lproj/Localizable.strings}
# second argument set check script path
readonly CHECK_SCRIPT=${2:-${PROJECT_DIR}/build-scripts/xcode/build_phases/common/localization_check}
${CHECK_SCRIPT} -master ${LOCALIZATION_PATH}

View File

@ -0,0 +1,5 @@
class Array
def nilOrEmpty?
self.nil? or self.empty?
end
end

View File

@ -0,0 +1,6 @@
class CommandUtils
def self.make_command(command)
command = command.to_s
return `#{command}`
end
end

View File

@ -0,0 +1,35 @@
require_relative 'array_extension.rb'
require_relative 'command_utils.rb'
require_relative 'string_extension.rb'
class GitСaretaker < CommandUtils
def self.get_modified_files
non_indexed_files = get_files_from('git diff --name-only | sed s/.*/"&,"/ ')
indexed_files = get_files_from('git diff --cached --name-only | sed s/.*/"&,"/ ')
modified_files = non_indexed_files + indexed_files
unique_modified_files = modified_files.uniq
unique_modified_swift_files = []
if not unique_modified_files.nilOrEmpty?
unique_modified_swift_files = unique_modified_files.select { |file_path|
file_path.to_s.filter_allowed_symbol_into_path
file_path.to_s.include? '.swift'
}
end
return unique_modified_swift_files
end
def self.get_creation_date(file_path)
git_command = 'git log --follow --format=%cD --reverse -- ' + file_path + ' | head -1'
return make_command(git_command)
end
private
def self.get_files_from(command)
files_as_string = make_command(command)
return files_as_string.split(',')
end
end

View File

@ -0,0 +1,78 @@
require 'optparse'
require 'ostruct'
require_relative 'array_extension.rb'
class SettingOption
def initialize
@options = OpenStruct.new
OptionParser.new do |opt|
opt.on('-p',
'--project_root_path STRING',
'The path of project directory and contains *.xcodeproj file always. ' +
'Example: project_root_path=~/Projects/MyProject/Source/..') { |option|
@options.project_root_path = option
}
opt.on('-r',
'--source_root_path STRING',
'The path of source directory and may not contains *.xcodeproj file in some cases. ' +
'Example: source_root_path=~/Projects/MyProject/') { |option|
@options.source_root_path = option
}
opt.on('-s',
'--swiftlint_executable_path STRING',
'The executable path of swiftlint') { |option|
@options.swiftlint_executable_path = option
}
opt.on('-c',
'--check_mode MODE',
'The mode of check is "fully" or "simplified"') { |option|
@options.check_mode = option
}
opt.on('-u',
'--use_multiple BOOL',
'The flag indicates the use of multiple yaml swiftlint configurations') { |option|
@options.use_multiple = option
}
opt.on('-d',
'--source_date DATE',
'The date of grouping files according touchin and old swiftlint rules') { |option|
@options.source_date = option
}
opt.on('-tc',
'--touchin_swiftlint_yaml_path STRING',
'The path to the touchin swiftlint yaml relative to the source directory') { |option|
@options.touchin_swiftlint_yaml_path = option
}
opt.on('-oc',
'--old_swiftlint_yaml_path STRING',
'The path to the old swiftlint yaml relative to the source directory') { |option|
@options.old_swiftlint_yaml_path = option
}
end.parse!
if @options.check_mode.to_s.nilOrEmpty?
@options.check_mode = 'fully'
end
if @options.use_multiple.to_s.nilOrEmpty?
@options.use_multiple = 'false'
end
if @options.source_root_path.to_s.nilOrEmpty?
@options.source_root_path = @options.project_root_path
end
if @options.touchin_swiftlint_yaml_path.to_s.nilOrEmpty?
@options.touchin_swiftlint_yaml_path = File.join(project_root_path, 'build-scripts/xcode/.swiftlint.yml')
end
if @options.old_swiftlint_yaml_path.to_s.nilOrEmpty?
@options.old_swiftlint_yaml_path = File.join(project_root_path, '.swiftlint.yml')
end
end
def method_missing(method, *args, &block)
@options.send(method, *args, &block)
end
end

View File

@ -0,0 +1,161 @@
require 'fileutils'
require 'tmpdir'
require_relative 'array_extension.rb'
require_relative 'git_caretaker.rb'
require_relative 'string_extension.rb'
require_relative 'swift_file_manager.rb'
require_relative 'yaml_manager.rb'
class StrategyMaker
def initialize(project_root_path, swiftlint_executable_path, touchin_swiftlint_yaml_path, old_swiftlint_yaml_path)
@project_root_path = project_root_path
@touchin_swiftlint_yaml_path = touchin_swiftlint_yaml_path
@old_swiftlint_yaml_path = old_swiftlint_yaml_path
@temporary_swiftlint_folder_name = Dir.mktmpdir
@touchin_swiftlint_yaml_temporary_path = File.join(@temporary_swiftlint_folder_name, '.touchin_swiftlint.yml')
@old_swiftlint_yaml_temporary_path = File.join(@temporary_swiftlint_folder_name, '.old_swiftlint.yml')
@swiftlint_autocorrect_command = swiftlint_executable_path + ' autocorrect --path ' + @project_root_path + ' --config '
@swiftlint_lint_command = swiftlint_executable_path + ' --path ' + @project_root_path + ' --config '
end
def run_fully_multiple_strategy(source_date)
create_yaml_managers_and_copy_temporary_files
exclude_files = unique_exclude_files(@touchin_swiftlint_yaml_manager, @old_swiftlint_yaml_manager)
swift_files = SwiftFileManager.new(exclude_files, source_date)
swift_files.find_list_file_paths(@project_root_path)
total_touchin_excluded_files = exclude_files + swift_files.old_files
total_old_excluded_files = exclude_files + swift_files.new_files
@touchin_swiftlint_yaml_manager.update('excluded', total_touchin_excluded_files)
@old_swiftlint_yaml_manager.update('excluded', total_old_excluded_files)
run_multiple_strategy(@touchin_swiftlint_yaml_temporary_path, @old_swiftlint_yaml_temporary_path)
end
def run_simplified_multiple_strategy(source_date, source_root_path)
included_files = GitСaretaker.get_modified_files
if included_files.nilOrEmpty?
puts 'Git did not found swift files to check'
return
end
create_yaml_managers_and_copy_temporary_files
exclude_files = unique_exclude_files(@touchin_swiftlint_yaml_manager, @old_swiftlint_yaml_manager)
included_files = included_files.map { |file_path| source_root_path + file_path }
swift_file_manager = SwiftFileManager.new(exclude_files, source_date)
swift_file_manager.find_list_file_paths_from(included_files)
total_touchin_included_files = swift_file_manager.new_files
total_old_included_files = swift_file_manager.old_files
@touchin_swiftlint_yaml_manager.update('excluded', [])
@old_swiftlint_yaml_manager.update('excluded', [])
@touchin_swiftlint_yaml_manager.update('included', total_touchin_included_files)
@old_swiftlint_yaml_manager.update('included', total_old_included_files)
is_exist_total_touchin_included_files = (not total_touchin_included_files.nilOrEmpty?)
is_exist_total_old_included_files = (not total_old_included_files.nilOrEmpty?)
if is_exist_total_touchin_included_files and is_exist_total_old_included_files
run_multiple_strategy(@touchin_swiftlint_yaml_temporary_path, @old_swiftlint_yaml_temporary_path)
elsif is_exist_total_touchin_included_files and not is_exist_total_old_included_files
run_single_strategy(@touchin_swiftlint_yaml_temporary_path)
elsif not is_exist_total_touchin_included_files and is_exist_total_old_included_files
run_single_strategy(@old_swiftlint_yaml_temporary_path)
else
puts 'Git did not found swift files to check'
end
end
def run_fully_single_strategy
run_single_strategy(@touchin_swiftlint_yaml_path)
end
def run_simplified_single_strategy(source_root_path)
included_files = GitСaretaker.get_modified_files
if included_files.nilOrEmpty?
puts 'Git did not found swift files to check'
return
end
create_copy_temporary_touchin_files
touchin_swiftlint_yaml_manager = YamlManager.new(@touchin_swiftlint_yaml_temporary_path)
touchin_excluded_files = touchin_swiftlint_yaml_manager.get_configuration('excluded')
swift_files = SwiftFileManager.new(touchin_excluded_files, '')
included_files = included_files.select { |file_name| not swift_files.is_excluded_file(file_name) }
included_files = included_files.map { |file_path| source_root_path + file_path }
touchin_swiftlint_yaml_manager.update('excluded', [])
touchin_swiftlint_yaml_manager.update('included', included_files)
if not included_files.nilOrEmpty?
run_single_strategy(@touchin_swiftlint_yaml_temporary_path)
else
puts 'Git found the swift files to check, but they are excluded in yaml'
end
end
private
def run_single_strategy(swiftlint_yaml_path)
result_swiftlint_command = get_swiftlint_command(swiftlint_yaml_path)
puts result_swiftlint_command
run_bash_command(result_swiftlint_command)
end
def run_multiple_strategy(touchin_swiftlint_yaml_temporary_path, old_swiftlint_yaml_temporary_path)
touchin_swiftlint_command = get_swiftlint_command(touchin_swiftlint_yaml_temporary_path)
old_swiftlint_command = get_swiftlint_command(old_swiftlint_yaml_temporary_path)
result_swiftlint_command = touchin_swiftlint_command + ' && ' + old_swiftlint_command
puts result_swiftlint_command
run_bash_command(result_swiftlint_command)
end
def get_swiftlint_command(swiftlint_yaml_path)
autocorrect_command = @swiftlint_autocorrect_command + swiftlint_yaml_path
lint_command = @swiftlint_lint_command + swiftlint_yaml_path
return autocorrect_command + ' && ' + lint_command
end
def run_bash_command(bash_command)
exit (exec bash_command)
end
def create_yaml_managers_and_copy_temporary_files
create_copy_temporary_files
@touchin_swiftlint_yaml_manager = YamlManager.new(@touchin_swiftlint_yaml_temporary_path)
@old_swiftlint_yaml_manager = YamlManager.new(@old_swiftlint_yaml_temporary_path)
end
def create_copy_temporary_files
create_copy_temporary_touchin_files
FileUtils.cp @old_swiftlint_yaml_path, @old_swiftlint_yaml_temporary_path
end
def create_copy_temporary_touchin_files
Dir.mkdir(@temporary_swiftlint_folder_name) unless Dir.exist?(@temporary_swiftlint_folder_name)
FileUtils.cp @touchin_swiftlint_yaml_path, @touchin_swiftlint_yaml_temporary_path
end
def unique_exclude_files(touchin_swiftlint_yaml_manager, old_swiftlint_yaml_manager)
touchin_excluded_files = touchin_swiftlint_yaml_manager.get_configuration('excluded')
old_excluded_files = old_swiftlint_yaml_manager.get_configuration('excluded')
common_exclude_files = touchin_excluded_files + old_excluded_files
unique_exclude_files = common_exclude_files.uniq
return unique_exclude_files
end
end

View File

@ -0,0 +1,17 @@
class String
def with_wrapped_whitespace
self.gsub(/\s+/, '\ ')
end
def filter_allowed_symbol_into_path
self.gsub!(/[^0-9A-Za-z \-+.\/]/, '')
end
def true?
self.to_s.downcase == "true"
end
def nilOrEmpty?
self.nil? or self.empty?
end
end

View File

@ -0,0 +1,68 @@
require 'fileutils'
require 'date'
require_relative 'git_caretaker.rb'
class SwiftFileManager
def initialize(excluded_files, source_date)
if not source_date.nilOrEmpty?
@source_date = Date.parse(source_date)
end
@excluded_files = excluded_files
@new_files = []
@old_files = []
end
def old_files
@old_files
end
def new_files
@new_files
end
def find_list_file_paths(start_folder)
swift_files = File.join('**', '*.swift')
Dir.glob(swift_files, base: start_folder) { |file_path|
if not is_excluded_file(file_path)
compare_timestamp(file_path)
end
}
end
def find_list_file_paths_from(files_path)
files_path.each { |file_path|
if not is_excluded_file(file_path)
compare_timestamp(file_path)
end
}
end
def is_excluded_file(file_path)
@excluded_files.each do |exclude_file_path|
if file_path.include? exclude_file_path
return true
end
end
return false
end
private
def compare_timestamp(file_path)
wrapped_whitespace_file_path = file_path.with_wrapped_whitespace
creation_date_string = GitСaretaker.get_creation_date(wrapped_whitespace_file_path)
if creation_date_string.nilOrEmpty?
@old_files.push(file_path)
puts ('Creation date of ' + file_path + ' was not found')
else
creation_date = Date.parse(creation_date_string)
puts ('Creation date of ' + file_path + ' is ' + creation_date.to_s)
if @source_date < creation_date
@new_files.push(file_path)
else
@old_files.push(file_path)
end
end
end
end

View File

@ -0,0 +1,19 @@
#https://github.com/TouchInstinct/Styleguide/blob/multiple_swiftlint/IOS/Guides/BuildScripts/Multiple_Swiftlint_Guide.md
require_relative 'setting_option.rb'
require_relative 'strategy_maker.rb'
setting = SettingOption.new
strategy_maker = StrategyMaker.new(setting.project_root_path,
setting.swiftlint_executable_path,
setting.touchin_swiftlint_yaml_path,
setting.old_swiftlint_yaml_path)
if setting.check_mode.eql? 'fully' and setting.use_multiple.true?
strategy_maker.run_fully_multiple_strategy(setting.source_date)
elsif setting.check_mode.eql? 'fully' and not setting.use_multiple.true?
strategy_maker.run_fully_single_strategy
elsif setting.check_mode.eql? 'simplified' and setting.use_multiple.true?
strategy_maker.run_simplified_multiple_strategy(setting.source_date, setting.source_root_path)
elsif setting.check_mode.eql? 'simplified' and not setting.use_multiple.true?
strategy_maker.run_simplified_single_strategy(setting.source_root_path)
end

View File

@ -0,0 +1,24 @@
require 'yaml'
require 'fileutils'
class YamlManager
def initialize(swiftlint_yaml_path)
@swiftlint_yaml_path = swiftlint_yaml_path
@configuration ||= YAML.load(File.read(@swiftlint_yaml_path))
end
def get_configuration(key)
@configuration[key]
end
def update(key, new_configuration_values)
@configuration[key] = new_configuration_values
save_settings(@configuration)
end
private
def save_settings(settings)
File.write(@swiftlint_yaml_path, settings.to_yaml)
end
end

88
xcode/build_phases/swiftlint.sh Normal file → Executable file
View File

@ -1,2 +1,86 @@
SOURCES_DIR=${1:-${TARGET_NAME}} # first argument or TARGET_NAME
${PODS_ROOT}/SwiftLint/swiftlint autocorrect --path ${SOURCES_DIR} --config ${PROJECT_DIR}/build-scripts/xcode/.swiftlint.yml && ${PODS_ROOT}/SwiftLint/swiftlint --path ${SOURCES_DIR} --config ${PROJECT_DIR}/build-scripts/xcode/.swiftlint.yml
#!/bin/sh
# Description:
# Runs swiftlint with selected or default config file.
#
# Parameters:
# $1 - path to swiftlint executable.
# $2 - path to swiftlint config.
#
# Required environment variables:
# SCRIPT_DIR - directory of current script.
# SRCROOT - project directory.
# PODS_ROOT - cocoapods installation directory (eg. ${SRCROOT}/Pods).
#
# Optional environment variables:
# SWIFTLINT_EXECUTABLE - path to swiftlint executable.
# SWIFTLINT_CONFIG_PATH - path to swiftlint config.
# SCRIPT_INPUT_FILE_COUNT - number of files listed in "Input files" of build phase.
# SCRIPT_INPUT_FILE_{N} - file path to directory that should be checked.
# FORCE_LINT - lint all project.
#
# Example of usage:
# swiftlint.sh
# FORCE_LINT=true; swiftlint.sh
# swiftlint.sh Pods/Swiftlint/swiftlint build-scripts/xcode/.swiftlint.yml
#
readonly SOURCES_DIRS=`. ${SCRIPT_DIR}/common/read_input_file_names.sh "\n" ${SRCROOT}`
if [ -z "${SWIFTLINT_EXECUTABLE}" ]; then
if [ ! -z "${1}" ]; then
readonly SWIFTLINT_EXECUTABLE=${1}
else
readonly SWIFTLINT_EXECUTABLE=${PODS_ROOT}/SwiftLint/swiftlint
fi
fi
if [ -z "${SWIFTLINT_CONFIG_PATH}" ]; then
if [ ! -z "${2}" ]; then
readonly SWIFTLINT_CONFIG_PATH=${2}
else
readonly SWIFTLINT_CONFIG_PATH=${SCRIPT_DIR}/../.swiftlint.yml
fi
fi
if [ ! -z "${FORCE_LINT}" ]; then
# Если задана переменная FORCE_LINT, то проверяем все файлы проекта
for SOURCE_DIR in ${SOURCES_DIRS}; do
${SWIFTLINT_EXECUTABLE} autocorrect --path ${SOURCE_DIR} --config ${SWIFTLINT_CONFIG_PATH}
${SWIFTLINT_EXECUTABLE} --path ${SOURCE_DIR} --config ${SWIFTLINT_CONFIG_PATH}
done
else
# Xcode упадет, если будем использовать большое количество Script Input Files,
# так как просто переполнится стек - https://unix.stackexchange.com/questions/357843/setting-a-long-environment-variable-breaks-a-lot-of-commands
# Поэтому воспользуемся "скрытым" параметром Swiflint - https://github.com/realm/SwiftLint/pull/3313
# Создадим временный файл swiftlint_files с префиксом @ и в нем уже определим список файлов
# необходимых для линтовки :)
lint_files_path="${SRCROOT}/build_phases/swiftlint_files"
# Если файл существует, то просто его очистим, если нет - создадим
> ${lint_files_path}
# Проходимся по папкам, которые требуют линтовки
for SOURCE_DIR in ${SOURCES_DIRS}; do
# Путь к папке репозитория
path_prefix="`git rev-parse --show-toplevel`/"
# Отбираем файлы, которые были изменены или созданы
source_unstaged_files=$(git diff --diff-filter=d --name-only --line-prefix=${path_prefix} ${SOURCE_DIR} | grep "\.swift$")
source_staged_files=$(git diff --diff-filter=d --name-only --line-prefix=${path_prefix} --cached ${SOURCE_DIR} | grep "\.swift$")
if [ ! -z "${source_unstaged_files}" ]; then
echo "${source_unstaged_files}" >> ${lint_files_path}
fi
if [ ! -z "${source_staged_files}" ]; then
echo "${source_staged_files}" >> ${lint_files_path}
fi
done
swiftlint_files_path="@${lint_files_path}"
${SWIFTLINT_EXECUTABLE} autocorrect --path ${swiftlint_files_path} --config ${SWIFTLINT_CONFIG_PATH} --force-exclude --use-alternative-excluding
${SWIFTLINT_EXECUTABLE} --path ${swiftlint_files_path} --config ${SWIFTLINT_CONFIG_PATH} --force-exclude --use-alternative-excluding
fi

View File

@ -1,4 +1,5 @@
arguments=("$@")
ignored_files=$(IFS=, ; echo "${arguments[*]}")
readonly ARGUMENTS=("$@")
readonly IGNORED_FILES=$(IFS=, ; echo "${ARGUMENTS[*]}")
readonly SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
ruby ${PROJECT_DIR}/build-scripts/xcode/build_phases/Unused.rb --config ${PROJECT_DIR}/build-scripts/xcode/UnusedConfig.yml --exclude ${ignored_files}
ruby ${SCRIPT_DIR}/Unused.rb --config ${SCRIPT_DIR}/../UnusedConfig.yml --exclude ${IGNORED_FILES}

View File

@ -0,0 +1,8 @@
readonly SOURCES_DIR=${1:-${PROJECT_DIR}/${PRODUCT_NAME}} # first argument set product dir
readonly UNUSED_RESOURCES_SCRIPT=${2:-${PROJECT_DIR}/build-scripts/xcode/build_phases/common/unused_resources} # second argument set script path
readonly REPORTS_DIR=${PROJECT_DIR}/code-quality-reports
readonly FILES_TO_EXCLUDE=`find ${SOURCES_DIR} -type d -name Localization -or -name Generated | paste -sd " " -`
mkdir ${REPORTS_DIR}
${UNUSED_RESOURCES_SCRIPT} --project ${SOURCES_DIR} --exclude ${FILES_TO_EXCLUDE} --action "l" > ${REPORTS_DIR}/Unused_resources_log.txt

View File

@ -1,6 +1,15 @@
$appName = File.basename(Dir['../*.xcworkspace'].first, '.*')
require_relative 'fastlane/touchlane/lib/touchlane'
require_relative 'managers/managers'
# ugly hack to add support for custom storage
Match.module_eval do
def self.storage_modes
return %w(git google_cloud s3 local)
end
end
private_lane :installDependencies do |options|
podsReposPath = File.expand_path "~/.cocoapods/repos/master/"
@ -11,49 +20,15 @@ private_lane :installDependencies do |options|
sh("rm -rf #{podsReposPath}")
end
if File.exists? "../Gemfile"
bundle_install(path: "../.gem")
end
cocoapods(
repo_update: true
try_repo_update_on_error: true
)
if File.exists? "../Cartfile"
use_rome = File.exists? "../Romefile"
swift_version = sh("xcrun swift --version | head -1 | sed 's/.*\\(\(.*\)\\).*/\\1/' | tr -d \"()\" | tr \" \" \"-\"").chop
rome_path = "Pods/Rome/rome"
rome_options = "--platform iOS --cache-prefix #{swift_version} --romefile Romefile"
carthage_install = lambda do
if use_rome
sh("cd .. && #{rome_path} download #{rome_options}")
end
carthage(command: "bootstrap", platform: "iOS", cache_builds: true)
if use_rome
sh("cd .. && #{rome_path} list --missing #{rome_options} | awk '{print $1}' | xargs -I framework_name #{rome_path} upload framework_name #{rome_options}")
end
end
begin
carthage_install.call
rescue
# workaround for https://github.com/Carthage/Carthage/issues/2298
sh("rm -rf ~/Library/Caches/org.carthage.CarthageKit")
carthage_install.call
end
end
end
private_lane :uploadToFirebase do |options|
releaseNotesFile = "release-notes.txt"
sh("touch ../#{releaseNotesFile}")
sh("yarn install")
app_target_folder_name = options[:appName] || $appName
configuration_type = Touchlane::ConfigurationType.from_type(options[:type])
@ -61,37 +36,47 @@ private_lane :uploadToFirebase do |options|
google_app_id = get_info_plist_value(path: gsp_plist_path, key: "GOOGLE_APP_ID")
firebase_app_distribution(
firebase_app_distibution_groups_path = File.expand_path "../firebase_app_distribution_groups"
# Select groups_file or groups parameter depending on groups file existence
if File.exists? firebase_app_distibution_groups_path
firebase_app_distribution(
app: google_app_id,
ipa_path: options[:ipa_path],
groups_file: firebase_app_distibution_groups_path,
release_notes_file: releaseNotesFile
)
else
firebase_app_distribution(
app: google_app_id,
ipa_path: options[:ipa_path],
groups: "touch-instinct",
release_notes_file: releaseNotesFile,
firebase_cli_path: File.expand_path("../node_modules/firebase-tools/lib/bin/firebase.js")
)
upload_symbols_to_crashlytics(
gsp_path: get_google_services_plist_path(app_target_folder_name, configuration_type)
)
release_notes_file: releaseNotesFile
)
end
end
private_lane :uploadToAppStore do |options|
def upload_to_app_store_using_options(options)
upload_to_app_store(
username: options[:username] || options[:apple_id],
api_key_path: options[:api_key_path],
api_key: options[:api_key],
ipa: options[:ipa_path],
force: true, # skip metainfo prompt
skip_metadata: true,
team_id: options[:itc_team_id],
dev_portal_team_id: options[:team_id]
dev_portal_team_id: options[:team_id],
precheck_include_in_app_purchases: false
)
end
private_lane :addShield do |options|
buildNumber = options[:buildNumber]
buildDescription = options[:xcconfig_name] # EnterpriseCustomerDev1WithoutSSLPinningRelease
buildDescription = options[:lane_name] # EnterpriseCustomerDev1WithoutSSLPinningRelease
.split(/(?=[A-Z])/) # -> ["Enterprise", "Customer", "Dev1", "Without", "S", "S", "L", "Pinning", "Release"]
.map { |v| v.gsub(/[[:lower:]]+/, "") }[1..2] # -> ["E", "C", "D1", "W", "S", "S", "L", "P", "R"] -> ["C", "D1"]
.join # -> "CD1"
begin
add_badge(
shield: "#{buildDescription}-#{buildNumber}-green",
@ -103,35 +88,40 @@ private_lane :addShield do |options|
end
private_lane :buildConfiguration do |options|
appName = options[:appName] || $appName
options[:appName] = options[:appName] || $appName
lane_name = lane_context[SharedValues::LANE_NAME]
lane_name = options[:lane_name] || lane_context[SharedValues::LANE_NAME]
options[:scheme] = appName
options[:xcconfig_name] = lane_name
options[:scheme] = options[:scheme] || options[:appName]
options[:lane_name] = lane_name
ipa_name = "#{options[:appName]}.ipa"
options[:output_name] = ipa_name
options[:ipa_path] = "./#{ipa_name}"
options[:dsym_path] = "./#{options[:appName]}.app.dSYM.zip"
options[:xcodeproj_path] = options[:xcodeproj_path] || "../#{options[:appName]}.xcodeproj"
options[:workspace] = options[:workspace] || File.expand_path("../#{options[:appName]}.xcworkspace")
configuration_type = Touchlane::ConfigurationType.from_lane_name(lane_name)
options = fill_up_options_using_configuration_type(options, configuration_type)
generate_xcodeproj_if_needed(options)
openKeychain(options)
if is_ci
if !options[:buildNumber].nil?
increment_build_number(
build_number: options[:buildNumber]
)
end
ipa_name = "#{appName}.ipa"
options[:output_name] = ipa_name
options[:ipa_path] = "./#{ipa_name}"
options[:dsym_path] = "./#{appName}.app.dSYM.zip"
options[:xcodeproj_path] = "../#{appName}.xcodeproj"
options[:workspace] = "./#{appName}.xcworkspace"
installDependencies(options)
run_code_generation_phase_if_needed(options)
generate_enabled_features_extension_if_needed(options)
if !(options[:uploadToFabric] || options[:uploadToAppStore])
options[:skip_package_ipa] = true
@ -144,49 +134,70 @@ private_lane :buildConfiguration do |options|
sync_code_signing_using_options(options)
addShield(options)
buildArchive(options)
uploadToFirebase(options)
end
if options[:uploadToAppStore]
options[:compileBitcode] = options[:compileBitcode].nil? ? true : options[:compileBitcode]
options[:include_symbols] = options[:include_symbols].nil? ? true : options[:include_symbols]
sync_code_signing_using_options(options)
buildArchive(options)
uploadToAppStore(options)
upload_to_app_store_using_options(options)
end
upload_symbols_to_crashlytics(
gsp_path: get_google_services_plist_path(app_target_folder_name, configuration_type)
)
end
private_lane :buildArchive do |options|
require 'json'
icloudEnvironment = options[:iCloudContainerEnvironment] || ""
exportOptions = icloudEnvironment.to_s.empty? ? {} : {iCloudContainerEnvironment: icloudEnvironment}
exportOptions[:compileBitcode] = options[:compileBitcode] || false
xcconfig_name = options[:xcconfig_name]
lane_name = options[:lane_name]
configuration = options[:configuration]
xcodeproj_path = options[:xcodeproj_path]
xcode_version = options[:xcodeVersion]
set_xcconfig_for_configuration_of_project(xcconfig_name, configuration, xcodeproj_path)
cmd = 'system_profiler -json SPDeveloperToolsDataType'
cmd_result = `#{cmd}`
spdeveloperToolsDataType = JSON.parse(cmd_result)['SPDeveloperToolsDataType']
sortedSPDeveloperToolsDataType = spdeveloperToolsDataType.sort_by { |hash| hash['spdevtools_version'].split(' ').first.to_i } # sort by increasing the version of xcode
default_xcode_version = sortedSPDeveloperToolsDataType.last['spdevtools_version'] # take the largest version in format: "13.0 (13A5212g)"
default_xcode_version_number = default_xcode_version.split(' ').first # take version number
if configuration != "AppStore" # AppStore uses xcconfig choosen in Xcode
set_xcconfig_for_configuration_of_project(lane_name, configuration, xcodeproj_path)
end
if xcode_version.nil?
xcversion(version: default_xcode_version_number)
else
xcversion(version: xcode_version)
end
gym(
clean: true,
workspace: options[:workspace],
scheme: options[:scheme],
archive_path: "./",
output_directory: "./",
archive_path: "./#{$appName}.xcarchive",
buildlog_path: "./",
output_name: options[:output_name],
configuration: configuration,
export_method: options[:export_method],
export_options: exportOptions,
skip_package_ipa: options[:skip_package_ipa],
include_symbols: options[:include_symbols] || false,
include_bitcode: options[:compileBitcode] || false,
include_symbols: options[:include_symbols] || false
)
end
lane :createPushCertificate do |options|
lane :CreatePushCertificate do |options|
configuration = get_configuration_for_type(options[:type] || "development")
options = configuration.to_options.merge(options)
certificates_path = File.expand_path "../Certificates"
Dir.mkdir(certificates_path) unless File.directory?(certificates_path)
@ -212,36 +223,6 @@ lane :SyncCodeSigning do |options|
sync_code_signing_using_options(options)
end
lane :SyncSymbols do |options|
configuration = get_configuration_for_type(options[:type])
options = configuration.to_options.merge(options)
appName = options[:appName] || $appName
xcodeproj_path = File.expand_path "../#{appName}.xcodeproj"
version_number = options[:version] || get_version_number(xcodeproj: xcodeproj_path, target: appName)
build_number = options[:build_number] || get_build_number(xcodeproj: xcodeproj_path)
if configuration.type.is_app_store
download_dsyms(
username: options[:username],
app_identifier: options[:app_identifier].first,
team_id: options[:itc_team_id],
version: version_number,
build_number: build_number
)
end
app_target_folder_name = appName
upload_symbols_to_crashlytics(
gsp_path: get_google_services_plist_path(app_target_folder_name, configuration.type)
)
clean_build_artifacts
end
private_lane :openKeychain do |options|
if is_ci?
# workaround to avoid duplication problem
@ -253,7 +234,7 @@ private_lane :openKeychain do |options|
name: options[:keychain_name],
password: options[:keychain_password],
unlock: true,
timeout: false,
timeout: 0,
add_to_search_list: !keychain_exists
)
else
@ -265,25 +246,18 @@ private_lane :openKeychain do |options|
end
lane :ManuallyUpdateCodeSigning do |options|
# based on this article https://medium.com/@jonathancardoso/using-fastlane-match-with-existing-certificates-without-revoking-them-a325be69dac6
require 'fastlane_core'
register_local_storage_for_match()
require 'match'
conf = FastlaneCore::Configuration.create(Match::Options.available_options, {})
conf.load_configuration_file("Matchfile")
git_url = conf.config_file_options[:git_url]
shallow_clone = false
branch = 'fastlane_certificates'
storage_conf = lambda do
new_storage = Match::Storage.for_mode('git', { git_url: git_url, shallow_clone: shallow_clone, git_branch: branch, clone_branch_directly: false})
storage_factory = lambda do
new_storage = Match::Storage.for_mode('local', { git_url: get_signing_identities_path() })
new_storage.download
return new_storage
end
encryption_conf_for_storage = lambda do |stor|
new_encryption = Match::Encryption.for_storage_mode('git', { git_url: git_url, working_directory: stor.working_directory})
encryption_factory = lambda do |stor|
new_encryption = Match::Encryption.for_storage_mode('local', { working_directory: stor.working_directory })
new_encryption.decrypt_files
return new_encryption
end
@ -292,8 +266,8 @@ lane :ManuallyUpdateCodeSigning do |options|
Dir[File.join(stor.working_directory, "**", "*.{cer,p12,mobileprovision}")]
end
storage = storage_conf.call
encryption = encryption_conf_for_storage.call(storage)
storage = storage_factory.call
encryption = encryption_factory.call(storage)
old_files = get_all_files.call(storage)
sh("open #{storage.working_directory}")
@ -316,8 +290,8 @@ lane :ManuallyUpdateCodeSigning do |options|
# to avoid this we use storage twice if needed
if files_diff.length > 0
storage = storage_conf.call
encryption = encryption_conf_for_storage.call(storage)
storage = storage_factory.call
encryption = encryption_factory.call(storage)
files_to_delete = files_diff.map do |file|
old_file = file
@ -334,32 +308,85 @@ lane :ManuallyUpdateCodeSigning do |options|
end
def sync_code_signing_using_options(options)
register_local_storage_for_match()
match(
app_identifier: options[:app_identifier],
username: options[:username] || options[:apple_id],
api_key_path: options[:api_key_path],
api_key: options[:api_key],
team_id: options[:team_id],
type: options[:type],
readonly: options[:readonly].nil? ? true : options[:readonly],
storage_mode: "git",
git_url: options[:git_url],
git_branch: "fastlane_certificates",
shallow_clone: true,
clone_branch_directly: true,
keychain_name: options[:keychain_name],
keychain_password: options[:keychain_password],
storage_mode: "local",
# we can't pass signing_identities_path as parameter name since params is hardcoded in match/runner.rb
git_url: get_signing_identities_path(),
skip_docs: true,
platform: "ios"
keychain_name: options[:keychain_name],
keychain_password: options[:keychain_password]
)
end
def register_local_storage_for_match
Match::Storage.register_backend(type: 'local', storage_class: Touchlane::LocalStorage)
Match::Encryption.register_backend(type: 'local', encryption_class: Match::Encryption::OpenSSL)
end
def get_signing_identities_path
File.expand_path "../EncryptedSigningIdentities"
end
def fill_up_options_using_configuration_type(options, configuration_type)
configuration = get_configuration_for_type(configuration_type.type)
configuration.to_options
api_key_path = File.expand_path "../fastlane/#{configuration_type.prefix}_api_key.json"
is_api_key_file_exists = File.exists?(api_key_path)
# default_options required to be empty due to the possibility of skipping the configuration type check below
default_options = {}
# Check whether configuration type is required to configure one of api key parameters or not
if configuration_type.is_app_store || configuration_type.is_development
# Check whether API key JSON file exists or not
if is_api_key_file_exists
# If exists then fill in all required information through api_key_path parameter
# and set a value to an options` parameter respectively
default_options = {:api_key_path => api_key_path}
else
# If doesn't exist then build api_key parameter through app_store_connect_api_key action
# and set a value to an options` parameter respectively also
default_options = {:api_key => get_app_store_connect_api_key()}
end
end
default_options
.merge(configuration.to_options)
.merge(get_keychain_options(options))
.merge(options)
end
def get_app_store_connect_api_key()
require 'json'
api_key_parameters = JSON.parse(ENV['API_KEY_JSON'])
return app_store_connect_api_key(
key_id: api_key_parameters['key_id'],
issuer_id: api_key_parameters['issuer_id'],
key_content: api_key_parameters['key'],
duration: api_key_parameters['duration'],
in_house: api_key_parameters['in_house']
)
end
def get_keychain_options(options)
keychain_name = options[:keychain_name]
keychain_password = options[:keychain_password]
@ -384,10 +411,38 @@ def get_configuration_for_type(type)
end
def get_google_services_plist_path(app_target_folder_name, configuration_type)
File.expand_path "../#{app_target_folder_name}/Resources/#{configuration_type.prefix}-GoogleService-Info.plist"
File.expand_path "../#{app_target_folder_name}/Resources/GoogleService-Info.plist"
end
def set_xcconfig_for_configuration_of_project(xcconfig_name, configuration, xcodeproj_path)
def generate_enabled_features_extension_if_needed(options)
app_target_folder_name = options[:appName] || $appName
project_enabled_features_file_path = File.expand_path "../#{app_target_folder_name}/Resources/Features/Enabled.swift"
build_settings_file_path = File.expand_path "../common/build_settings.yaml"
unless is_feature_extension_needed?(options, project_enabled_features_file_path)
return
end
if options[:features].nil?
builder_features_list = [] # If Enabled.swift exists and features option is nil we need to create empty extension to avoid unexpected features
else
builder_features_list = options[:features]
.split(",").map { |feature_name| feature_name.strip } # [ "Feature1", "Feature2", "Feature3" ]
end
build_settings_features_list = Managers::FileManager.load_from_file_YAML(build_settings_file_path)["features"]
enabled_features_extension = Touchlane::Features.generate_enabled_features_extension(builder_features_list, build_settings_features_list)
Managers::FileManager.save_data_to_file(project_enabled_features_file_path, enabled_features_extension)
end
def is_feature_extension_needed?(options, project_enabled_features_file_path)
!options[:features].nil? || File.exists?(project_enabled_features_file_path)
end
def set_xcconfig_for_configuration_of_project(lane_name, configuration, xcodeproj_path)
require 'xcodeproj'
project = Xcodeproj::Project.open(xcodeproj_path)
@ -395,7 +450,8 @@ def set_xcconfig_for_configuration_of_project(xcconfig_name, configuration, xcod
target_to_modify_selector = lambda do |t|
supported_product_types = [
Xcodeproj::Constants::PRODUCT_TYPE_UTI[:application],
Xcodeproj::Constants::PRODUCT_TYPE_UTI[:app_extension]
Xcodeproj::Constants::PRODUCT_TYPE_UTI[:app_extension],
Xcodeproj::Constants::PRODUCT_TYPE_UTI[:framework]
]
return !t.test_target_type? && supported_product_types.include?(t.product_type)
end
@ -403,12 +459,35 @@ def set_xcconfig_for_configuration_of_project(xcconfig_name, configuration, xcod
application_targets = project.native_targets.select(&target_to_modify_selector)
application_targets.each do |target|
build_configuration = target.build_configuration_list[configuration]
config_name = target.name + xcconfig_name
config_name = target.name + lane_name
build_configuration_reference = project.files.select { |f| f.path.start_with?(config_name) }.first
build_configuration.base_configuration_reference = build_configuration_reference
if !build_configuration_reference.nil? # target has custom xcconfig
build_configuration = target.build_configuration_list[configuration]
build_configuration.base_configuration_reference = build_configuration_reference
end
end
project.save()
end
def generate_xcodeproj_if_needed(options)
project_yml_path = File.expand_path "../project.yml"
if !File.exists?(options[:xcodeproj_path]) && File.exists?(project_yml_path)
xcodegen(
spec: project_yml_path
)
end
end
# Build phases
def run_code_generation_phase_if_needed(options)
code_generation_script_path = File.expand_path "../.githooks/scripts/CodeGen.sh"
if File.exists? code_generation_script_path
sh(code_generation_script_path, options[:workspace])
end
end

@ -1 +1 @@
Subproject commit 3f81529c1425a74f43fe84fe8befffd81a442cc7
Subproject commit e591d65722d78918c84c4eeda3df8c4012e75323

View File

@ -0,0 +1,132 @@
require 'json'
require 'mustache'
require 'yaml'
require_relative '../fastlane/touchlane/lib/touchlane/configuration_type'
class String
def in_current_dir
"#{__dir__}/#{self}"
end
end
class ConfigRenderer
class XCConfigKeys
DEVELOPMENT_TEAM = "DEVELOPMENT_TEAM"
PRODUCT_BUNDLE_IDENTIFIER = "PRODUCT_BUNDLE_IDENTIFIER"
CODE_SIGN_STYLE = "CODE_SIGN_STYLE"
end
INHERITED_PREFIX = "$(inherited)"
private_constant :INHERITED_PREFIX
def initialize(configurations_file_path, build_parameters_path, configs_folder_name)
@configurations_file_path = configurations_file_path
@build_parameters_path = build_parameters_path
@configs_folder_name = configs_folder_name
end
def render_xconfigs
temp_configs_data_file_path = "configs_data.json".in_current_dir
generator_path = "build_options_helper/helper.py".in_current_dir
template_path = "target_xcconfig.mustache".in_current_dir
# Create config directory if needed
Dir.mkdir(@configs_folder_name) unless Dir.exist?(@configs_folder_name)
# Call python script and generate configs to config file
system("python #{generator_path} -bp #{@build_parameters_path} -o #{__dir__} -r ios_build_settings -p ios")
# Open settings, configurations and template files
target_xcconfig_tempate = File.read(template_path)
$configurations = YAML.load(File.open(@configurations_file_path))
$config_types = $configurations["types"]
targets = $configurations["targets"]
# Run through all target in project
targets.each do |target_name, target|
# Need open everytime, because script make some changes only for this target
configs = JSON.load(File.open(temp_configs_data_file_path))
# Run through all configs
configs.each do |config|
# Take default values
distribution_type = Touchlane::ConfigurationType.from_account_type(config["account_type"]).type
properties = target[distribution_type]
# Add properties from settings file
properties.each do |key, value|
if config["xcconfig_options"].any? { |option| key == option["key"] }
config["xcconfig_options"].map! { |option| key == option["key"] ? merge_config_data(key, option["value"], value) : option }
else
config["xcconfig_options"].append(config_option(key, value))
end
end
# Add missing properties if needed
config["xcconfig_options"].concat(generate_missing_properties(target_name, properties, distribution_type))
# Create settings pack
config_data = {
"target_name": target_name,
"abstract_targets_prefix": target["abstract_targets_prefix"],
"configuration": config
}
# Create file for every setting in loop
File.open(@configs_folder_name + "/" + target_name + config["name"] + ".xcconfig", 'w') { |file|
file.puts(Mustache.render(target_xcconfig_tempate, config_data))
}
end
end
# Remove config file, it's trash
File.delete(temp_configs_data_file_path) if File.exist?(temp_configs_data_file_path)
end
# Make tuple of key and value become mustache template element
def config_option(key, value)
return { "key" => key, "value" => value }
end
def merge_config_data(key, config_value, settings_value)
if settings_value.start_with?(INHERITED_PREFIX)
new_value = settings_value.split(INHERITED_PREFIX).last
return config_option(key, config_value + new_value)
else
return config_option(key, settings_value)
end
end
# Fetch development team from build configuration
def fetch_development_team(development_team_key, distribution_type)
current_config = $config_types[distribution_type]
team_value = current_config["team_id"]
return config_option(development_team_key, team_value)
end
# Generate missing properties if needed
def generate_missing_properties(target_name, properties, distribution_type)
result = []
# Bundle_id_key should be among the properties (required by fastlane)
unless properties.key?(XCConfigKeys::PRODUCT_BUNDLE_IDENTIFIER)
raise "#{target_name}: Could not find #{XCConfigKeys::PRODUCT_BUNDLE_IDENTIFIER} for #{distribution_type}"
end
unless properties.key?(XCConfigKeys::DEVELOPMENT_TEAM)
result.append(fetch_development_team(XCConfigKeys::DEVELOPMENT_TEAM, distribution_type))
end
unless properties.key?(XCConfigKeys::CODE_SIGN_STYLE)
result.append(config_option(XCConfigKeys::CODE_SIGN_STYLE, "Manual"))
end
return result
end
end

View File

@ -1,13 +1,16 @@
targets:
TestProject:
abstract_targets_prefix: "-TestProjectKit"
development:
PRODUCT_BUNDLE_IDENTIFIER: "ru.touchin.testproject"
PROVISIONING_PROFILE_SPECIFIER: "TestProjectDev"
CODE_SIGN_ENTITLEMENTS: "TestProject/Standard.entitlements"
SWIFT_ACTIVE_COMPILATION_CONDITIONS: "$(inherited) DEBUG_MENU"
enterprise:
PRODUCT_BUNDLE_IDENTIFIER: "com.touchin.testproject"
PROVISIONING_PROFILE_SPECIFIER: "TestProjectEnterprise"
CODE_SIGN_ENTITLEMENTS: "TestProject/Enterprise.entitlements"
SWIFT_ACTIVE_COMPILATION_CONDITIONS: "$(inherited) DEBUG_MENU"
appstore:
PRODUCT_BUNDLE_IDENTIFIER: "ru.customer.domain"
PROVISIONING_PROFILE_SPECIFIER: "TestProjectAppStore"
@ -15,13 +18,13 @@ targets:
types:
development:
apple_id: "apple@touchin.ru"
apple_id: "iosdev@touchin.ru"
team_id: "**********"
itc_team_id: "**********"
enterprise:
apple_id: "enterpriseapple@touchin.ru"
team_id: "**********"
appstore:
apple_id: "apple@touchin.ru"
apple_id: "iosdev@touchin.ru"
team_id: "**********"
itc_team_id: "**********"

View File

@ -1,158 +1,15 @@
require 'json'
require 'mustache'
require 'yaml'
# Usage: render_xcconfigs.rb <CONFIGURATIONS.YAML PATH>
require_relative "config_renderer"
#
# Result: Adds .xcconfig files to $configs_folder_name directory.
# Usage: render_xcconfigs.rb <configurations.yaml> <build_parameters.yaml> [<ouptut folder>]
#
# Result: Adds .xcconfig files to ouptut folder.
# Files are only being added and changed, not removed!
# It is recommended to remove old .xcconfig files before running this script.
# Constants
$configs_folder_name = "TargetConfigurations"
class String
def in_current_dir
"#{__dir__}/#{self}"
end
end
#
# Input files paths
configurations_file_path = ARGV[0]
temp_configs_data_file_path = "configs_data.json".in_current_dir
generator_path = "build_options_helper/helper.py".in_current_dir
template_path = "target_xcconfig.mustache".in_current_dir
build_parameters_path = ARGV[1] || "build_parameters.yaml".in_current_dir
build_parameters_path = ARGV[1]
configs_folder_name = ARGV[2] || "TargetConfigurations"
# Create config directory if needed
Dir.mkdir($configs_folder_name) unless Dir.exist?($configs_folder_name)
# Call python script and generate configs to config file
system("python #{generator_path} -bp #{build_parameters_path} -o #{__dir__} -r ios_build_settings -p ios")
# Open settings, configurations and template files
target_xcconfig_tempate = File.read(template_path)
$configurations = YAML.load(File.open(configurations_file_path))
$config_types = $configurations["types"]
# Set global property
targets = $configurations["targets"]
# Make tuple of key and value become mustache template element
def config_option(key, value)
return { "key" => key, "value" => value }
end
# Maps lane prefix to distribution type
def distribution_type_of(account_type)
case account_type
when "Standard"
"development"
when "Enterprise"
"enterprise"
when "AppStore"
"appstore"
else
raise "Error: Unsupported distribution type #{account_type}"
end
end
# Fetch development team from build configuration
def fetch_development_team(development_team_key, distribution_type)
current_config = $config_types[distribution_type]
team_value = current_config["team_id"]
return config_option(development_team_key, team_value)
end
# Return empty array or generated provisioning profile hash
def generate_provisioning_profile(provisioning_key, bundle_id, distribution_type)
case distribution_type
when "appstore"
app_store_profile = "match AppStore " + bundle_id
config_option(provisioning_key, app_store_profile)
else
config_option(provisioning_key, bundle_id)
end
end
def generate_google_service_info_plist_path(google_service_info_plist_key, target_name, distribution_type)
google_service_info_plist_path = target_name + "/Resources/"
path_suffix = case distribution_type
when "development"
"Standard-GoogleService-Info.plist"
when "enterprise"
"Enterprise-GoogleService-Info.plist"
else
"AppStore-GoogleService-Info.plist"
end
return config_option(google_service_info_plist_key, google_service_info_plist_path + path_suffix)
end
# Generate missing properties if needed
def generate_missing_properties(target_name, properties, distribution_type)
result = []
development_team_key = "DEVELOPMENT_TEAM"
provisioning_key = "PROVISIONING_PROFILE_SPECIFIER"
google_service_info_plist_key = "GOOGLE_SERVICE_INFO_PLIST_PATH"
bundle_id_key = "PRODUCT_BUNDLE_IDENTIFIER"
# Bundle_id_key should be among the properties (required by fastlane)
unless properties.key?(bundle_id_key)
raise "#{target_name}: Could not find #{bundle_id_key} for #{distribution_type}"
end
unless properties.key?(development_team_key)
result.append(fetch_development_team(development_team_key, distribution_type))
end
unless properties.key?(provisioning_key)
result.append(generate_provisioning_profile(provisioning_key, properties[bundle_id_key], distribution_type))
end
unless properties.key?(google_service_info_plist_key)
result.append(generate_google_service_info_plist_path(google_service_info_plist_key, target_name, distribution_type))
end
return result
end
# Run through all target in project
targets.each do |target_name, target|
# Need open everytime, because script make some changes only for this target
configs = JSON.load(File.open(temp_configs_data_file_path))
# Run through all configs
configs.each do |config|
# Take default values
distribution_type = distribution_type_of(config["account_type"])
properties = target[distribution_type]
# Add properties from settings file
properties.each do |key, value|
config["xcconfig_options"].append(config_option(key, value))
end
# Add missing properties if needed
config["xcconfig_options"].concat(generate_missing_properties(target_name, properties, distribution_type))
# Create settings pack
config_data = {
"target_name": target_name,
"configuration": config
}
# Create file for every setting in loop
File.open($configs_folder_name + "/" + target_name + config["name"] + ".xcconfig", 'w') { |file|
file.puts(Mustache.render(target_xcconfig_tempate, config_data))
}
end
end
# Remove config file, it's trash
File.delete(temp_configs_data_file_path) if File.exist?(temp_configs_data_file_path)
ConfigRenderer.new(configurations_file_path, build_parameters_path, configs_folder_name).render_xconfigs()

View File

@ -1,7 +1,5 @@
#include "Pods/Target Support Files/Pods-{{target_name}}/Pods-{{target_name}}.{{configuration.build_type}}.xcconfig"
#include "Pods/Target Support Files/Pods{{abstract_targets_prefix}}-{{target_name}}/Pods{{abstract_targets_prefix}}-{{target_name}}.{{configuration.build_type}}.xcconfig"
{{#configuration.xcconfig_options}}
{{key}} = {{value}}
{{/configuration.xcconfig_options}}
CODE_SIGN_STYLE = Manual
{{/configuration.xcconfig_options}}

View File

@ -0,0 +1,84 @@
require 'match'
require 'fileutils'
require 'fastlane_core/ui/ui'
module Touchlane
class LocalStorage < Match::Storage::Interface
attr_accessor :signing_identities_path
def self.configure(params)
return self.new(
# we can't pass signing_identities_path since params is hardcoded in match/runner.rb
signing_identities_path: params[:git_url]
)
end
def initialize(signing_identities_path: nil)
self.signing_identities_path = signing_identities_path
end
def prefixed_working_directory
return working_directory
end
def download
# Check if we already have a functional working_directory
return if @working_directory
# No existing working directory, creating a new one now
self.working_directory = Dir.mktmpdir
Dir.mkdir(self.signing_identities_path) unless File.exists?(self.signing_identities_path)
FileUtils.cp_r("#{self.signing_identities_path}/.", self.working_directory)
end
def human_readable_description
"Local folder [#{self.signing_identities_path}]"
end
def upload_files(files_to_upload: [], custom_message: nil)
# `files_to_upload` is an array of files that need to be moved to signing identities dir
# Those doesn't mean they're new, it might just be they're changed
# Either way, we'll upload them using the same technique
files_to_upload.each do |current_file|
# Go from
# "/var/folders/px/bz2kts9n69g8crgv4jpjh6b40000gn/T/d20181026-96528-1av4gge/profiles/development/Development_me.mobileprovision"
# to
# "profiles/development/Development_me.mobileprovision"
#
# We also have to remove the trailing `/` as Google Cloud doesn't handle it nicely
target_path = current_file.gsub(self.working_directory + "/", "")
absolute_target_path = File.join(self.signing_identities_path, target_path)
FileUtils.mkdir_p(File.dirname(absolute_target_path))
FileUtils.cp_r(current_file, absolute_target_path, remove_destination: true)
end
end
def delete_files(files_to_delete: [], custom_message: nil)
files_to_delete.each do |file_name|
target_path = file_name.gsub(self.working_directory + "/", "")
File.delete(File.join(self.signing_identities_path, target_path))
end
end
def skip_docs
false
end
def list_files(file_name: "", file_ext: "")
Dir[File.join(working_directory, "**", file_name, "*.#{file_ext}")]
end
def generate_matchfile_content
path = Fastlane::UI.input("Path to the signing identities folder: ")
return "git_url(\"#{path}\")"
end
end
end

View File

@ -1,4 +1,6 @@
module Touchlane
require_relative "configuration_type"
require_relative "configuration"
end
require_relative "touchlane/configuration_type"
require_relative "touchlane/configuration"
require_relative "touchlane/features"
require_relative "match/storage/local_storage"
end

View File

@ -14,16 +14,21 @@ module Touchlane
def initialize(type)
@type = type
@is_app_store = type == APP_STORE
@is_development = type == DEVELOPMENT
case type
when DEVELOPMENT, ENTERPRISE
when DEVELOPMENT
@export_method = type
@configuration = type == DEVELOPMENT ? "Debug" : "Release"
@is_app_store = false
@prefix = type == DEVELOPMENT ? DEVELOPMENT_PREFIX : ENTERPRISE_PREFIX
@configuration = "Debug"
@prefix = DEVELOPMENT_PREFIX
when ENTERPRISE
@export_method = type
@configuration = "Release"
@prefix = ENTERPRISE_PREFIX
when APP_STORE
@export_method = "app-store"
@configuration = "AppStore"
@is_app_store = true
@prefix = APP_STORE_PREFIX
else
raise "Unknown type passed #{type}"
@ -32,7 +37,7 @@ module Touchlane
private_class_method :new
attr_reader :export_method, :type, :configuration, :is_app_store, :prefix
attr_reader :export_method, :type, :configuration, :is_app_store, :is_development, :prefix
def self.from_lane_name(lane_name)
case
@ -52,6 +57,20 @@ module Touchlane
new(type)
end
def self.from_account_type(account_type)
case account_type
when DEVELOPMENT_PREFIX
from_type(DEVELOPMENT)
when ENTERPRISE_PREFIX
from_type(ENTERPRISE)
when APP_STORE_PREFIX
from_type(APP_STORE)
else
raise "Unable to map #{account_type} to #{ConfigurationType.class}."
+ "Available account types: #{DEVELOPMENT_PREFIX}, #{ENTERPRISE_PREFIX}, #{APP_STORE_PREFIX}"
end
end
def to_options
{
:type => @type,

View File

@ -0,0 +1,26 @@
require_relative '../../../../managers/managers'
require_relative '../../../../templates/templates'
module Touchlane
class Features
def self.generate_enabled_features_extension(builder_features_list, build_settings_features_list)
# Check is entered features contains in configuration file
features_diff = builder_features_list - build_settings_features_list
unless features_diff.empty?
raise "Unexpected features: " + features_diff.join(', ')
end
# Generate enabled features extension from feature names
enabled_features_extension_template = Templates::FeatureTemplates.enabled_features_extension
utils = Managers::TemplateManager.new(builder_features_list)
utils.render(enabled_features_extension_template).strip
end
private_class_method :new
end
end

View File

@ -0,0 +1,33 @@
require 'yaml'
require 'json'
module Managers
class FileManager
def self.save_data_to_file(path, data)
unless File.exists? path
raise "Unable to save data to file at #{path}"
else
File.open(path, "w") do |f|
f.write(data)
end
end
end
def self.load_from_file_YAML(path)
unless File.exists? path
raise "Unable to load data from file at #{path}"
else
YAML.load_file(path)
end
end
def self.save_data_to_file_in_json(path, data)
json_data = JSON.pretty_generate(data)
save_data_to_file(path, json_data)
end
private_class_method :new
end
end

View File

@ -0,0 +1,19 @@
require 'erb'
module Managers
class TemplateManager
include ERB::Util
attr_accessor :items
def initialize(items)
@items = items
end
def render(template)
ERB.new(template).result(binding)
end
end
end

View File

@ -0,0 +1,4 @@
module Managers
require_relative "lib/file_manager"
require_relative "lib/template_manager"
end

View File

@ -0,0 +1,3 @@
module Templates
require_relative "templates/features_templates"
end

View File

@ -0,0 +1,34 @@
module Templates
module FeatureTemplates
def self.features_enum
"
// MARK: - Generated feature toggles
public enum Feature: String, Codable, RawRepresentable, CaseIterable {
<% for @item in @items %>
case <%= @item %>
<% end %>
}
"
end
def self.enabled_features_extension
"
// MARK: - Generated enabled features
public extension Feature {
static var enabled: [Feature] {
[
<% for @item in @items %>
\.<%= @item %>,
<% end %>
]
}
}
"
end
end
end