Compare commits
20 Commits
feature/up
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
dd85251813 | |
|
|
069396ecb8 | |
|
|
fce05b99ec | |
|
|
efa00ff67b | |
|
|
565279ef75 | |
|
|
66f4df9425 | |
|
|
5a663334f0 | |
|
|
ccd4df9314 | |
|
|
d900c5b9ad | |
|
|
1160ea6320 | |
|
|
e732e16d21 | |
|
|
abe62fca64 | |
|
|
7f7d535ca5 | |
|
|
e54d505272 | |
|
|
70c2899bf0 | |
|
|
434c4f4e6a | |
|
|
e44811d1b7 | |
|
|
eaad4027c6 | |
|
|
5be3987ea3 | |
|
|
f8865b3232 |
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
### Как подключить к проекту:
|
### Как подключить к проекту:
|
||||||
|
|
||||||
1. Скопировать директорию [`codeStyles`](/codeStyles) в директорию проекта `.idea`
|
1. Скопировать директорию [`codeStyles`](./codeStyles) в директорию проекта `.idea`
|
||||||
2. Добавить в файл `.gitignore` строку `!.idea/codeStyles`
|
2. Добавить в файл `.gitignore` строку `!.idea/codeStyles`
|
||||||
3. Перезапустить Android Studio, чтобы настройки применились
|
3. Перезапустить Android Studio, чтобы настройки применились
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
### Как импортировать схему в Android Studio:
|
### Как импортировать схему в Android Studio:
|
||||||
|
|
||||||
1. Скачать схему [`codeStyles/Project.xml`](/codeStyles/Project.xml)
|
1. Скачать схему [`codeStyles/Project.xml`](./codeStyles/Project.xml)
|
||||||
2. В Android Studio перейти в `File` > `Settings` > `Editor` > `Code Style`
|
2. В Android Studio перейти в `File` > `Settings` > `Editor` > `Code Style`
|
||||||
3. Нажать на шестеренку справа от выпадающего списка схем и выбрать `Import Scheme`
|
3. Нажать на шестеренку справа от выпадающего списка схем и выбрать `Import Scheme`
|
||||||
4. В открывшемся окне указать путь до сохраненной схемы и нажать `ОК`
|
4. В открывшемся окне указать путь до сохраненной схемы и нажать `ОК`
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,7 @@ class StaticAnalysisAndroidPlugin : StaticAnalysisPlugin() {
|
||||||
project.tasks.register("staticAnalysis") {
|
project.tasks.register("staticAnalysis") {
|
||||||
setupStaticAnalysisTask(
|
setupStaticAnalysisTask(
|
||||||
linters = linters,
|
linters = linters,
|
||||||
buildVariant = applicationVariants
|
buildVariant = applicationVariants.first { it.name.contains("Debug") }.name
|
||||||
.map { it.name.toLowerCase() }
|
|
||||||
.first { it.contains("debug") }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,14 +32,10 @@ class StaticAnalysisAndroidPlugin : StaticAnalysisPlugin() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createLinters(extension: StaticAnalysisExtension): List<Linter> = mutableListOf<Linter>().apply {
|
override fun createLinters(): List<Linter> = listOf(
|
||||||
add(DetektLinter())
|
DetektLinter(),
|
||||||
if (extension.needCpdLinterEnabled) {
|
CpdLinter(),
|
||||||
add(CpdLinter())
|
AndroidLinter()
|
||||||
}
|
)
|
||||||
if (extension.needAndroidLinterEnabled) {
|
|
||||||
add(AndroidLinter())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class StaticAnalysisBackendPlugin : StaticAnalysisPlugin() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createLinters(extension: StaticAnalysisExtension): List<Linter> = listOf(
|
override fun createLinters(): List<Linter> = listOf(
|
||||||
CpdLinter(),
|
CpdLinter(),
|
||||||
DetektLinter()
|
DetektLinter()
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,5 @@ package static_analysis.plugins
|
||||||
|
|
||||||
open class StaticAnalysisExtension(
|
open class StaticAnalysisExtension(
|
||||||
var excludes: String = "",
|
var excludes: String = "",
|
||||||
var buildScriptDir: String? = null,
|
var buildScriptDir: String? = null
|
||||||
var needCpdLinterEnabled: Boolean = true,
|
|
||||||
var needAndroidLinterEnabled: Boolean = false,
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package static_analysis.plugins
|
package static_analysis.plugins
|
||||||
|
|
||||||
import org.gradle.api.Action
|
|
||||||
import org.gradle.api.Plugin
|
import org.gradle.api.Plugin
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.Task
|
import org.gradle.api.Task
|
||||||
|
|
@ -25,10 +24,9 @@ abstract class StaticAnalysisPlugin : Plugin<Project> {
|
||||||
|
|
||||||
extensions.create<StaticAnalysisExtension>(STATIC_ANALYSIS_EXT_NAME)
|
extensions.create<StaticAnalysisExtension>(STATIC_ANALYSIS_EXT_NAME)
|
||||||
|
|
||||||
val initData: StaticAnalysisExtension = extensions.getByType()
|
val linters = createLinters()
|
||||||
val linters = createLinters(initData)
|
|
||||||
|
|
||||||
linters.forEach { it.setupForProject(target, initData) }
|
linters.forEach { it.setupForProject(target, extensions.getByType()) }
|
||||||
|
|
||||||
gradle.projectsEvaluated {
|
gradle.projectsEvaluated {
|
||||||
createStaticAnalysisTasks(target, linters)
|
createStaticAnalysisTasks(target, linters)
|
||||||
|
|
@ -41,26 +39,7 @@ abstract class StaticAnalysisPlugin : Plugin<Project> {
|
||||||
dependsOn(*(linters.map { it.getTaskNames(project, buildVariant) }.flatten().toTypedArray()))
|
dependsOn(*(linters.map { it.getTaskNames(project, buildVariant) }.flatten().toTypedArray()))
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun createLinters(extension: StaticAnalysisExtension): List<Linter>
|
abstract fun createLinters(): List<Linter>
|
||||||
|
|
||||||
abstract fun createStaticAnalysisTasks(project: Project, linters: List<Linter>)
|
abstract fun createStaticAnalysisTasks(project: Project, linters: List<Linter>)
|
||||||
|
|
||||||
/**
|
|
||||||
* Project level
|
|
||||||
*
|
|
||||||
* В gradle.kts используйте этот extension.
|
|
||||||
*
|
|
||||||
* В классическом gradle, синтаксис ровно тот, же, но без использования extension.
|
|
||||||
* ```
|
|
||||||
* staticAnalysis {
|
|
||||||
* excludes = ":name_example"
|
|
||||||
* buildScriptDir = buildScriptsDirectory
|
|
||||||
* needCpdLinterEnabled = false
|
|
||||||
* needAndroidLinterEnabled = false
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
fun Project.staticAnalysis(configure: Action<StaticAnalysisExtension>): Unit =
|
|
||||||
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure(STATIC_ANALYSIS_EXT_NAME, configure)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ complexity:
|
||||||
ignoreSingleWhenExpression: true
|
ignoreSingleWhenExpression: true
|
||||||
ignoreSimpleWhenEntries: true
|
ignoreSimpleWhenEntries: true
|
||||||
LabeledExpression:
|
LabeledExpression:
|
||||||
active: false
|
active: true
|
||||||
LargeClass:
|
LargeClass:
|
||||||
active: true
|
active: true
|
||||||
threshold: 800
|
threshold: 800
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ RESET := $(shell tput -Txterm sgr0)
|
||||||
RUBY_VERSION="2.7.6"
|
RUBY_VERSION="2.7.6"
|
||||||
|
|
||||||
open_project=(open *.xcworkspace)
|
open_project=(open *.xcworkspace)
|
||||||
install_dev_certs=(bundle exec fastlane SyncCodeSigning type:development readonly:true)
|
install_dev_certs=(bundle exec fastlane InstallDevelopmentSigningIdentities)
|
||||||
install_pods=(bundle exec pod install || bundle exec pod install --repo-update)
|
install_pods=(bundle exec pod install || bundle exec pod install --repo-update)
|
||||||
init_rbenv=(if command -v rbenv &> /dev/null; then eval "$$(rbenv init -)"; fi)
|
init_rbenv=(if command -v rbenv &> /dev/null; then eval "$$(rbenv init -)"; fi)
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ init:
|
||||||
|
|
||||||
bundle install
|
bundle install
|
||||||
|
|
||||||
$(call gen)
|
xcodegen
|
||||||
|
|
||||||
$(call install_pods)
|
$(call install_pods)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Description:
|
||||||
|
# Runs full linting and copy-paste-detection for project
|
||||||
|
#
|
||||||
|
# Required environment variables:
|
||||||
|
# SRCROOT - project directory.
|
||||||
|
# SCRIPT_DIR - directory of current script.
|
||||||
|
#
|
||||||
|
# Optional environment variables:
|
||||||
|
# See swiftlint.sh and copy_paste_detection.sh for complete list of available variables
|
||||||
|
#
|
||||||
|
# Example of usage:
|
||||||
|
# ./full_code_lint.sh
|
||||||
|
#
|
||||||
|
|
||||||
|
if [ -z "${SCRIPT_DIR}" ]; then
|
||||||
|
SCRIPT_DIR=${SRCROOT}/build-scripts/xcode/build_phases
|
||||||
|
fi
|
||||||
|
|
||||||
|
. ${SRCROOT}/build-scripts/xcode/aux_scripts/install_env.sh swiftlint
|
||||||
|
FORCE_LINT=true; . ${SCRIPT_DIR}/swiftlint.sh
|
||||||
|
|
||||||
|
. ${SRCROOT}/build-scripts/xcode/aux_scripts/install_env.sh cpd
|
||||||
|
. ${SCRIPT_DIR}/copy_paste_detection.sh Localization Generated Pods
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Description:
|
||||||
|
# Runs incremental linting for project
|
||||||
|
#
|
||||||
|
# Required environment variables:
|
||||||
|
# SRCROOT - project directory.
|
||||||
|
# SCRIPT_DIR - directory of current script.
|
||||||
|
#
|
||||||
|
# Optional environment variables:
|
||||||
|
# See swiftlint.sh for complete list of available variables
|
||||||
|
#
|
||||||
|
# Example of usage:
|
||||||
|
# ./incremetal_code_lint.sh
|
||||||
|
#
|
||||||
|
|
||||||
|
if [ -z "${SCRIPT_DIR}" ]; then
|
||||||
|
SCRIPT_DIR=${SRCROOT}/build-scripts/xcode/build_phases
|
||||||
|
fi
|
||||||
|
|
||||||
|
. ${SRCROOT}/build-scripts/xcode/aux_scripts/install_env.sh swiftlint
|
||||||
|
. ${SCRIPT_DIR}/swiftlint.sh
|
||||||
|
|
@ -24,36 +24,50 @@
|
||||||
readonly EXIT_SUCCESS=0
|
readonly EXIT_SUCCESS=0
|
||||||
readonly EXIT_FAILURE=1
|
readonly EXIT_FAILURE=1
|
||||||
|
|
||||||
readonly TRUE=0
|
readonly TRUE=1
|
||||||
readonly FALSE=1
|
readonly FALSE=0
|
||||||
|
|
||||||
readonly LOG_TAG="API-GENERATOR"
|
readonly LOG_TAG="API-GENERATOR"
|
||||||
|
|
||||||
notice()
|
notice()
|
||||||
{
|
{
|
||||||
echo "${LOG_TAG}:NOTICE: ${1}"
|
echo "${LOG_TAG}:NOTICE: ${1}" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
debug()
|
debug()
|
||||||
{
|
{
|
||||||
if [ ! -z "${VERBOSE}" ]; then
|
if [ ! -z "${VERBOSE}" ]; then
|
||||||
echo "${LOG_TAG}:DEBUG: ${1}"
|
echo "${LOG_TAG}:DEBUG: ${1}" >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
exit_on_failure()
|
||||||
|
{
|
||||||
|
eval ${1}
|
||||||
|
|
||||||
|
local -r EXIT_CODE=$?
|
||||||
|
|
||||||
|
if [ ${EXIT_CODE} -ne 0 ]; then
|
||||||
|
echo "Recent command: \`${1}\` failed with code ${EXIT_CODE}"
|
||||||
|
|
||||||
|
exit ${EXIT_CODE}
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
is_force_run()
|
is_force_run()
|
||||||
{
|
{
|
||||||
if [ -z "${FORCE_RUN}" ]; then
|
if [ -z "${FORCE_RUN}" ]; then
|
||||||
return ${FALSE}
|
echo ${FALSE}
|
||||||
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local -r STR_MODE=`tr "[:upper:]" "[:lower:]" <<< ${FORCE_RUN}`
|
local -r STR_MODE=`tr "[:upper:]" "[:lower:]" <<< ${FORCE_RUN}`
|
||||||
|
|
||||||
if [ ${STR_MODE} == "yes" ] || [ ${STR_MODE} == "true" ] || [ ${STR_MODE} == "1" ]; then
|
if [ ${STR_MODE} == "yes" ] || [ ${STR_MODE} == "true" ] || [ ${STR_MODE} == "1" ]; then
|
||||||
return ${TRUE}
|
echo ${TRUE}
|
||||||
|
else
|
||||||
|
echo ${FALSE}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return ${FALSE}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is_single_file()
|
is_single_file()
|
||||||
|
|
@ -85,6 +99,19 @@ get_api_spec_current_commit()
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_api_spec_status()
|
||||||
|
{
|
||||||
|
if [ -z "${API_SPEC_DIR}" ]; then
|
||||||
|
if [ ! -z "${1}" ]; then
|
||||||
|
echo `git -C ${1} status -s`
|
||||||
|
else
|
||||||
|
echo `git status -s`
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo `git -C ${API_SPEC_DIR} status -s`
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
is_api_spec_under_source_control()
|
is_api_spec_under_source_control()
|
||||||
{
|
{
|
||||||
local IS_UNDER_SOURCE_CONTROL_CHECK
|
local IS_UNDER_SOURCE_CONTROL_CHECK
|
||||||
|
|
@ -99,14 +126,33 @@ is_api_spec_under_source_control()
|
||||||
IS_UNDER_SOURCE_CONTROL_CHECK=`git -C ${API_SPEC_DIR} rev-parse --is-inside-work-tree 2>/dev/null`
|
IS_UNDER_SOURCE_CONTROL_CHECK=`git -C ${API_SPEC_DIR} rev-parse --is-inside-work-tree 2>/dev/null`
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[ ${IS_UNDER_SOURCE_CONTROL_CHECK} = "true" ]
|
if [ "${IS_UNDER_SOURCE_CONTROL_CHECK}" = "true" ]; then
|
||||||
|
echo ${TRUE}
|
||||||
|
else
|
||||||
|
echo ${FALSE}
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
is_api_spec_has_uncommited_changes()
|
||||||
|
{
|
||||||
|
if [ `is_api_spec_under_source_control` -eq ${TRUE} ]; then
|
||||||
|
local -r API_SPEC_STATUS=`get_api_spec_status`
|
||||||
|
|
||||||
|
if [ -z "${API_SPEC_STATUS}" ]; then
|
||||||
|
echo ${FALSE}
|
||||||
|
else
|
||||||
|
echo ${TRUE}
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo ${FALSE}
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
is_nothing_changed_since_last_check()
|
is_nothing_changed_since_last_check()
|
||||||
{
|
{
|
||||||
if is_force_run; then
|
if [ `is_force_run` -eq ${TRUE} ]; then
|
||||||
notice "Force run detected. Skipping commits comparison."
|
notice "Force run detected. Skipping commits comparison."
|
||||||
return ${EXIT_FAILURE}
|
echo ${TRUE}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "${COMMIT_FILE_PATH}" ]; then
|
if [ -z "${COMMIT_FILE_PATH}" ]; then
|
||||||
|
|
@ -114,28 +160,33 @@ is_nothing_changed_since_last_check()
|
||||||
local -r COMMIT_FILE_PATH=${1}
|
local -r COMMIT_FILE_PATH=${1}
|
||||||
else
|
else
|
||||||
debug "COMMIT_FILE_PATH should be defined or passed as first argument!"
|
debug "COMMIT_FILE_PATH should be defined or passed as first argument!"
|
||||||
return ${EXIT_FAILURE}
|
echo ${FALSE}
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if is_api_spec_under_source_control; then
|
if [ `is_api_spec_under_source_control` -eq ${TRUE} ]; then
|
||||||
local -r CURRENT_COMMIT=`get_api_spec_current_commit`
|
local -r CURRENT_COMMIT=`get_api_spec_current_commit`
|
||||||
|
|
||||||
local -r LAST_CHECKED_COMMIT=`cat ${COMMIT_FILE_PATH} 2> /dev/null || echo ""`
|
local -r LAST_CHECKED_COMMIT=`cat ${COMMIT_FILE_PATH} 2> /dev/null || echo ""`
|
||||||
|
|
||||||
if [ ${CURRENT_COMMIT} = "${LAST_CHECKED_COMMIT}" ]; then
|
if [ ${CURRENT_COMMIT} = "${LAST_CHECKED_COMMIT}" ]; then
|
||||||
return ${EXIT_SUCCESS}
|
if [ `is_api_spec_has_uncommited_changes` -eq ${TRUE} ]; then
|
||||||
|
notice "API spec has uncomitted changes."
|
||||||
|
echo ${FALSE}
|
||||||
|
else
|
||||||
|
echo ${TRUE}
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
return ${EXIT_FAILURE}
|
echo ${FALSE}
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
return ${EXIT_SUCCESS}
|
echo ${FALSE}
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
record_current_commit()
|
record_current_commit()
|
||||||
{
|
{
|
||||||
if is_force_run; then
|
if [ `is_force_run` -eq ${TRUE} ]; then
|
||||||
notice "Force run detected. Commit won't be recorder."
|
notice "Force run detected. Commit won't be recorder."
|
||||||
exit ${EXIT_SUCCESS}
|
exit ${EXIT_SUCCESS}
|
||||||
fi
|
fi
|
||||||
|
|
@ -204,7 +255,9 @@ openapi_codegen()
|
||||||
|
|
||||||
rm -rf ${OUTPUT_PATH}/${API_NAME} # remove previously generated API (if exists)
|
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}
|
local -r OPENAPI_COMMAND="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}"
|
||||||
|
|
||||||
|
exit_on_failure "${OPENAPI_COMMAND}"
|
||||||
|
|
||||||
# flatten folders hierarchy
|
# flatten folders hierarchy
|
||||||
|
|
||||||
|
|
@ -249,7 +302,9 @@ api_generator_codegen()
|
||||||
|
|
||||||
. build-scripts/xcode/aux_scripts/download_file.sh ${FILE_NAME} ${DOWNLOAD_URL}
|
. 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)
|
local -r API_GENERATOR_COMMAND="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)"
|
||||||
|
|
||||||
|
exit_on_failure "${API_GENERATOR_COMMAND}"
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly BUILD_PHASES_DIR=${SRCROOT}/build_phases
|
readonly BUILD_PHASES_DIR=${SRCROOT}/build_phases
|
||||||
|
|
@ -258,7 +313,7 @@ mkdir -p ${BUILD_PHASES_DIR}
|
||||||
|
|
||||||
readonly COMMIT_FILE_PATH=${BUILD_PHASES_DIR}/api-generator-commit
|
readonly COMMIT_FILE_PATH=${BUILD_PHASES_DIR}/api-generator-commit
|
||||||
|
|
||||||
if is_nothing_changed_since_last_check; then
|
if [ `is_nothing_changed_since_last_check` -eq ${TRUE} ]; then
|
||||||
notice "Nothing was changed. API generation skipped."
|
notice "Nothing was changed. API generation skipped."
|
||||||
exit ${EXIT_SUCCESS}
|
exit ${EXIT_SUCCESS}
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -61,13 +61,12 @@ if has_input_files && \
|
||||||
SCRIPT_INPUT_FILE_VARIABLE_NAME="SCRIPT_INPUT_FILE_${i}"
|
SCRIPT_INPUT_FILE_VARIABLE_NAME="SCRIPT_INPUT_FILE_${i}"
|
||||||
SHELL_VARIABLE="\${${SCRIPT_INPUT_FILE_VARIABLE_NAME}}"
|
SHELL_VARIABLE="\${${SCRIPT_INPUT_FILE_VARIABLE_NAME}}"
|
||||||
RESOLVED_FILE_NAME=`envsubst <<< ${SHELL_VARIABLE}`
|
RESOLVED_FILE_NAME=`envsubst <<< ${SHELL_VARIABLE}`
|
||||||
INPUT_FILE_NAMES=${INPUT_FILE_NAMES}${FILE_NAMES_SEPARATOR}${RESOLVED_FILE_NAME}
|
|
||||||
|
|
||||||
if [ ! -z ${INPUT_FILE_NAMES} ]; then
|
if [ ! -z ${INPUT_FILE_NAMES} ]; then
|
||||||
INPUT_FILE_NAMES=${INPUT_FILE_NAMES}${FILE_NAMES_SEPARATOR}
|
INPUT_FILE_NAMES=${INPUT_FILE_NAMES}${FILE_NAMES_SEPARATOR}
|
||||||
|
else
|
||||||
|
INPUT_FILE_NAMES=${INPUT_FILE_NAMES}${FILE_NAMES_SEPARATOR}${RESOLVED_FILE_NAME}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INPUT_FILE_NAMES=${INPUT_FILE_NAMES}${RESOLVED_FILE_NAME}
|
|
||||||
done
|
done
|
||||||
elif has_input_file_lists; then
|
elif has_input_file_lists; then
|
||||||
for i in `seq 0 $((${SCRIPT_INPUT_FILE_LIST_COUNT}-1))`
|
for i in `seq 0 $((${SCRIPT_INPUT_FILE_LIST_COUNT}-1))`
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
# $1 $2 $3 $n - folders to exclude from code checking.
|
# $1 $2 $3 $n - folders to exclude from code checking.
|
||||||
#
|
#
|
||||||
# Required environment variables:
|
# Required environment variables:
|
||||||
# PROJECT_DIR - project directory.
|
# SRCROOT - project directory.
|
||||||
# SCRIPT_DIR - directory of current script.
|
# SCRIPT_DIR - directory of current script.
|
||||||
#
|
#
|
||||||
# Optional environment variables:
|
# Optional environment variables:
|
||||||
|
|
@ -15,43 +15,47 @@
|
||||||
# SCRIPT_INPUT_FILE_{N} - file path to directory that should be checked.
|
# SCRIPT_INPUT_FILE_{N} - file path to directory that should be checked.
|
||||||
#
|
#
|
||||||
# Modified files:
|
# Modified files:
|
||||||
# ${PROJECT_DIR}/code-quality-reports/CPDLog.txt - check report.
|
# ${SRCROOT}/code-quality-reports/CPDLog.txt - check report.
|
||||||
#
|
#
|
||||||
# Example of usage:
|
# Example of usage:
|
||||||
# runner.sh copy_paste_detection.sh Generated Localization Pods
|
# copy_paste_detection.sh Generated Localization Pods
|
||||||
#
|
#
|
||||||
|
|
||||||
readonly EXIT_SUCCESS=0
|
EXIT_SUCCESS=0
|
||||||
readonly EXIT_FAILURE=1
|
EXIT_FAILURE=1
|
||||||
|
|
||||||
. ${SCRIPT_DIR}/../aux_scripts/install_env.sh pmd
|
|
||||||
|
|
||||||
if which pmd >/dev/null; then
|
if which pmd >/dev/null; then
|
||||||
readonly REPORTS_DIR="${PROJECT_DIR}/code-quality-reports"
|
REPORTS_DIR="${SRCROOT}/code-quality-reports"
|
||||||
|
|
||||||
readonly SOURCES_DIRS=`. ${SCRIPT_DIR}/common/read_input_file_names.sh " " ${PROJECT_DIR}`
|
SOURCES_DIRS=`. ${SCRIPT_DIR}/common/read_input_file_names.sh " " ${SRCROOT}`
|
||||||
|
|
||||||
readonly COMMAND_LINE_ARGUMENTS=$@
|
COMMAND_LINE_ARGUMENTS=$@
|
||||||
|
|
||||||
FOLDERS_TO_EXLUDE=""
|
FOLDERS_TO_EXCLUDE=""
|
||||||
|
|
||||||
for argument in ${COMMAND_LINE_ARGUMENTS}
|
for argument in ${COMMAND_LINE_ARGUMENTS}
|
||||||
do
|
do
|
||||||
FOLDERS_TO_EXLUDE=${FOLDERS_TO_EXLUDE}"-or -name ${argument} "
|
FOLDERS_TO_EXCLUDE=${FOLDERS_TO_EXCLUDE}"-or -name ${argument} "
|
||||||
done
|
done
|
||||||
|
|
||||||
FOLDERS_TO_EXLUDE=`echo ${FOLDERS_TO_EXLUDE} | cut -c5-` # remove first "-or"
|
FOLDERS_TO_EXCLUDE=`echo ${FOLDERS_TO_EXCLUDE} | cut -c5-` # remove first "-or"
|
||||||
|
|
||||||
readonly FILES_TO_EXCLUDE=`find ${PROJECT_DIR} -type d ${FOLDERS_TO_EXLUDE} | paste -sd " " -`
|
FILES_TO_EXCLUDE=`find ${SRCROOT} -type d ${FOLDERS_TO_EXCLUDE} | paste -sd " " -`
|
||||||
|
|
||||||
mkdir -p ${REPORTS_DIR}
|
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
|
DIRS_ARGUMENTS=""
|
||||||
|
|
||||||
|
for SOURCE_DIR in ${SOURCES_DIRS}; do
|
||||||
|
DIRS_ARGUMENTS=${DIRS_ARGUMENTS}" --dir "${SOURCE_DIR}
|
||||||
|
done
|
||||||
|
|
||||||
|
pmd cpd ${DIRS_ARGUMENTS} --exclude ${FILES_TO_EXCLUDE} --minimum-tokens 50 --language swift --encoding UTF-8 --format net.sourceforge.pmd.cpd.XMLRenderer --skip-lexical-errors true > ${REPORTS_DIR}/cpd-output.xml
|
||||||
|
|
||||||
php ${SCRIPT_DIR}/../aux_scripts/cpd_script.php ${REPORTS_DIR}/cpd-output.xml | tee ${REPORTS_DIR}/CPDLog.txt
|
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
|
# Make paths relative to SRCROOT, so different developers won't rewrite entire file
|
||||||
readonly SED_REPLACEMENT_STRING=$(echo ${PROJECT_DIR} | sed "s/\//\\\\\//g")
|
SED_REPLACEMENT_STRING=$(echo ${SRCROOT} | sed "s/\//\\\\\//g")
|
||||||
|
|
||||||
sed -i '' "s/${SED_REPLACEMENT_STRING}//g" "${REPORTS_DIR}/CPDLog.txt"
|
sed -i '' "s/${SED_REPLACEMENT_STRING}//g" "${REPORTS_DIR}/CPDLog.txt"
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,10 @@ readonly SOURCES_DIRS=`. ${SCRIPT_DIR}/common/read_input_file_names.sh "\n" ${SR
|
||||||
if [ -z "${SWIFTLINT_EXECUTABLE}" ]; then
|
if [ -z "${SWIFTLINT_EXECUTABLE}" ]; then
|
||||||
if [ ! -z "${1}" ]; then
|
if [ ! -z "${1}" ]; then
|
||||||
readonly SWIFTLINT_EXECUTABLE=${1}
|
readonly SWIFTLINT_EXECUTABLE=${1}
|
||||||
else
|
elif [ ! -z "${PODS_ROOT}" ]; then
|
||||||
readonly SWIFTLINT_EXECUTABLE=${PODS_ROOT}/SwiftLint/swiftlint
|
readonly SWIFTLINT_EXECUTABLE=${PODS_ROOT}/SwiftLint/swiftlint
|
||||||
|
else
|
||||||
|
readonly SWIFTLINT_EXECUTABLE=${SRCROOT}/Pods/SwiftLint/swiftlint
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,7 @@
|
||||||
$appName = File.basename(Dir['../*.xcworkspace'].first, '.*')
|
$appName = File.basename(Dir['../*.xcworkspace'].first, '.*')
|
||||||
|
|
||||||
require_relative 'fastlane/touchlane/lib/touchlane'
|
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|
|
private_lane :installDependencies do |options|
|
||||||
podsReposPath = File.expand_path "~/.cocoapods/repos/master/"
|
podsReposPath = File.expand_path "~/.cocoapods/repos/master/"
|
||||||
|
|
@ -56,13 +48,18 @@ private_lane :uploadToFirebase do |options|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload_to_app_store_using_options(options)
|
def upload_to_app_store_using_options(options, submit_for_review = false)
|
||||||
upload_to_app_store(
|
upload_to_app_store(
|
||||||
username: options[:username] || options[:apple_id],
|
username: options[:username] || options[:apple_id],
|
||||||
api_key_path: options[:api_key_path],
|
api_key_path: options[:api_key_path],
|
||||||
api_key: options[:api_key],
|
api_key: options[:api_key],
|
||||||
ipa: options[:ipa_path],
|
ipa: options[:ipa_path],
|
||||||
|
build_number: options[:ipa_path].nil? ? options[:buildNumber] : nil,
|
||||||
|
skip_binary_upload: options[:ipa_path].nil?,
|
||||||
|
skip_screenshots: true,
|
||||||
force: true, # skip metainfo prompt
|
force: true, # skip metainfo prompt
|
||||||
|
submit_for_review: submit_for_review,
|
||||||
|
submission_information: options[:submission_information],
|
||||||
skip_metadata: true,
|
skip_metadata: true,
|
||||||
team_id: options[:itc_team_id],
|
team_id: options[:itc_team_id],
|
||||||
dev_portal_team_id: options[:team_id],
|
dev_portal_team_id: options[:team_id],
|
||||||
|
|
@ -105,7 +102,7 @@ private_lane :buildConfiguration do |options|
|
||||||
options[:workspace] = options[:workspace] || File.expand_path("../#{options[:appName]}.xcworkspace")
|
options[:workspace] = options[:workspace] || File.expand_path("../#{options[:appName]}.xcworkspace")
|
||||||
|
|
||||||
configuration_type = Touchlane::ConfigurationType.from_lane_name(lane_name)
|
configuration_type = Touchlane::ConfigurationType.from_lane_name(lane_name)
|
||||||
options = fill_up_options_using_configuration_type(options, configuration_type)
|
options = fill_up_options_using_configuration_type(options, configuration_type, true)
|
||||||
|
|
||||||
generate_xcodeproj_if_needed(options)
|
generate_xcodeproj_if_needed(options)
|
||||||
|
|
||||||
|
|
@ -120,18 +117,17 @@ private_lane :buildConfiguration do |options|
|
||||||
installDependencies(options)
|
installDependencies(options)
|
||||||
|
|
||||||
run_code_generation_phase_if_needed(options)
|
run_code_generation_phase_if_needed(options)
|
||||||
generate_enabled_features_extension_if_needed(options)
|
|
||||||
|
|
||||||
if !(options[:uploadToFabric] || options[:uploadToAppStore])
|
if !(options[:uploadToFabric] || options[:uploadToAppStore])
|
||||||
options[:skip_package_ipa] = true
|
options[:skip_package_ipa] = true
|
||||||
|
|
||||||
sync_code_signing_using_options(options)
|
install_signing_identities(options)
|
||||||
|
|
||||||
buildArchive(options) # check build failures and static analysis
|
buildArchive(options) # check build failures and static analysis
|
||||||
end
|
end
|
||||||
|
|
||||||
if options[:uploadToFabric]
|
if options[:uploadToFabric]
|
||||||
sync_code_signing_using_options(options)
|
install_signing_identities(options)
|
||||||
addShield(options)
|
addShield(options)
|
||||||
buildArchive(options)
|
buildArchive(options)
|
||||||
uploadToFirebase(options)
|
uploadToFirebase(options)
|
||||||
|
|
@ -140,9 +136,9 @@ private_lane :buildConfiguration do |options|
|
||||||
if options[:uploadToAppStore]
|
if options[:uploadToAppStore]
|
||||||
options[:include_symbols] = options[:include_symbols].nil? ? true : options[:include_symbols]
|
options[:include_symbols] = options[:include_symbols].nil? ? true : options[:include_symbols]
|
||||||
|
|
||||||
sync_code_signing_using_options(options)
|
install_signing_identities(options)
|
||||||
buildArchive(options)
|
buildArchive(options)
|
||||||
upload_to_app_store_using_options(options)
|
upload_to_app_store_using_options(options, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
upload_symbols_to_crashlytics(
|
upload_symbols_to_crashlytics(
|
||||||
|
|
@ -160,25 +156,13 @@ private_lane :buildArchive do |options|
|
||||||
lane_name = options[:lane_name]
|
lane_name = options[:lane_name]
|
||||||
configuration = options[:configuration]
|
configuration = options[:configuration]
|
||||||
xcodeproj_path = options[:xcodeproj_path]
|
xcodeproj_path = options[:xcodeproj_path]
|
||||||
xcode_version = options[:xcodeVersion]
|
|
||||||
|
|
||||||
cmd = 'system_profiler -json SPDeveloperToolsDataType'
|
xcodes(select_for_current_build_only: true)
|
||||||
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
|
if configuration != "AppStore" # AppStore uses xcconfig choosen in Xcode
|
||||||
set_xcconfig_for_configuration_of_project(lane_name, configuration, xcodeproj_path)
|
set_xcconfig_for_configuration_of_project(lane_name, configuration, xcodeproj_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
if xcode_version.nil?
|
|
||||||
xcversion(version: default_xcode_version_number)
|
|
||||||
else
|
|
||||||
xcversion(version: xcode_version)
|
|
||||||
end
|
|
||||||
|
|
||||||
gym(
|
gym(
|
||||||
clean: true,
|
clean: true,
|
||||||
workspace: options[:workspace],
|
workspace: options[:workspace],
|
||||||
|
|
@ -194,33 +178,46 @@ private_lane :buildArchive do |options|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
lane :CreatePushCertificate do |options|
|
lane :SubmitForReview do |options|
|
||||||
configuration = get_configuration_for_type(options[:type] || "development")
|
configuration_type = Touchlane::ConfigurationType.from_type("appstore")
|
||||||
options = configuration.to_options.merge(options)
|
options = fill_up_options_using_configuration_type(options, configuration_type, false)
|
||||||
|
|
||||||
certificates_path = File.expand_path "../Certificates"
|
upload_to_app_store_using_options(options, true)
|
||||||
Dir.mkdir(certificates_path) unless File.directory?(certificates_path)
|
|
||||||
|
|
||||||
app_identifier = options[:app_identifier]
|
|
||||||
|
|
||||||
get_push_certificate(
|
|
||||||
development: options[:development].nil? ? true : options[:development],
|
|
||||||
generate_p12: true,
|
|
||||||
active_days_limit: 30, # create new certificate if old one will expire in 30 days
|
|
||||||
save_private_key: false,
|
|
||||||
app_identifier: (app_identifier.is_a? Array) ? app_identifier.first : app_identifier,
|
|
||||||
username: options[:username] || options[:apple_id],
|
|
||||||
team_id: options[:team_id],
|
|
||||||
p12_password: "123", # empty password won't work with Pusher
|
|
||||||
output_path: certificates_path
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
lane :SyncCodeSigning do |options|
|
lane :InstallDevelopmentSigningIdentities do |options|
|
||||||
configuration_type = Touchlane::ConfigurationType.from_type(options[:type])
|
configuration_type = Touchlane::ConfigurationType.from_type("development")
|
||||||
options = fill_up_options_using_configuration_type(options, configuration_type)
|
options = fill_up_options_using_configuration_type(options, configuration_type)
|
||||||
|
|
||||||
sync_code_signing_using_options(options)
|
install_signing_identities(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
lane :RefreshProfiles do |options|
|
||||||
|
type = options[:type] || "development"
|
||||||
|
|
||||||
|
configuration_type = Touchlane::ConfigurationType.from_type(type)
|
||||||
|
options = fill_up_options_using_configuration_type(options, configuration_type)
|
||||||
|
|
||||||
|
refresh_profiles(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
lane :ReplaceDevelopmentCertificate do |options|
|
||||||
|
configuration_type = Touchlane::ConfigurationType.from_type("development")
|
||||||
|
options = fill_up_options_using_configuration_type(options, configuration_type, true)
|
||||||
|
|
||||||
|
replace_development_certificate(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
lane :SyncAppStoreIdentities do |options|
|
||||||
|
configuration_type = Touchlane::ConfigurationType.from_type("appstore")
|
||||||
|
options = fill_up_options_using_configuration_type(options, configuration_type, true)
|
||||||
|
|
||||||
|
options[:readonly] = false
|
||||||
|
sync_signing_identities(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
lane :ManuallyUpdateCodeSigning do |options|
|
||||||
|
manually_update_code_signing(get_default_options.merge(options))
|
||||||
end
|
end
|
||||||
|
|
||||||
private_lane :openKeychain do |options|
|
private_lane :openKeychain do |options|
|
||||||
|
|
@ -245,98 +242,19 @@ private_lane :openKeychain do |options|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
lane :ManuallyUpdateCodeSigning do |options|
|
def get_default_options
|
||||||
register_local_storage_for_match()
|
{
|
||||||
|
:git_url => get_signing_identities_path(),
|
||||||
require 'match'
|
:signing_identities_path => get_signing_identities_path(),
|
||||||
|
:storage_mode => Touchlane::LocalStorage::STORAGE_TYPE
|
||||||
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_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
|
|
||||||
|
|
||||||
get_all_files = lambda do |stor|
|
|
||||||
Dir[File.join(stor.working_directory, "**", "*.{cer,p12,mobileprovision}")]
|
|
||||||
end
|
|
||||||
|
|
||||||
storage = storage_factory.call
|
|
||||||
encryption = encryption_factory.call(storage)
|
|
||||||
old_files = get_all_files.call(storage)
|
|
||||||
|
|
||||||
sh("open #{storage.working_directory}")
|
|
||||||
|
|
||||||
# we are not using prompt() since it requires non-empty input which is not a case for Enter (\n)
|
|
||||||
puts "Enter any key when you're done"
|
|
||||||
STDIN.gets
|
|
||||||
|
|
||||||
encryption.encrypt_files
|
|
||||||
|
|
||||||
files_to_commit = get_all_files.call(storage)
|
|
||||||
old_directory = storage.working_directory
|
|
||||||
storage.save_changes!(files_to_commit: files_to_commit)
|
|
||||||
|
|
||||||
|
|
||||||
# need to check, because saving changes with delete is another function (update repo if needed)
|
|
||||||
files_diff = old_files - files_to_commit
|
|
||||||
|
|
||||||
# match can not work with both save/delete functionality `You can't provide both files_to_delete and files_to_commit right now`
|
|
||||||
# to avoid this we use storage twice if needed
|
|
||||||
|
|
||||||
if files_diff.length > 0
|
|
||||||
storage = storage_factory.call
|
|
||||||
encryption = encryption_factory.call(storage)
|
|
||||||
|
|
||||||
files_to_delete = files_diff.map do |file|
|
|
||||||
old_file = file
|
|
||||||
old_file.slice! old_directory
|
|
||||||
new_file = File.join(storage.working_directory, old_file)
|
|
||||||
File.delete(new_file) if File.exist?(new_file)
|
|
||||||
file = new_file
|
|
||||||
end
|
|
||||||
|
|
||||||
encryption.encrypt_files
|
|
||||||
storage.save_changes!(files_to_delete: files_to_delete)
|
|
||||||
end
|
|
||||||
|
|
||||||
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: "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,
|
|
||||||
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
|
end
|
||||||
|
|
||||||
def get_signing_identities_path
|
def get_signing_identities_path
|
||||||
File.expand_path "../EncryptedSigningIdentities"
|
File.expand_path "../EncryptedSigningIdentities"
|
||||||
end
|
end
|
||||||
|
|
||||||
def fill_up_options_using_configuration_type(options, configuration_type)
|
def fill_up_options_using_configuration_type(options, configuration_type, keychain_password_required = false)
|
||||||
configuration = get_configuration_for_type(configuration_type.type)
|
configuration = get_configuration_for_type(configuration_type.type)
|
||||||
|
|
||||||
api_key_path = File.expand_path "../fastlane/#{configuration_type.prefix}_api_key.json"
|
api_key_path = File.expand_path "../fastlane/#{configuration_type.prefix}_api_key.json"
|
||||||
|
|
@ -344,7 +262,7 @@ def fill_up_options_using_configuration_type(options, configuration_type)
|
||||||
|
|
||||||
# default_options required to be empty due to the possibility of skipping the configuration type check below
|
# default_options required to be empty due to the possibility of skipping the configuration type check below
|
||||||
|
|
||||||
default_options = {}
|
default_options = get_default_options
|
||||||
|
|
||||||
# Check whether configuration type is required to configure one of api key parameters or not
|
# Check whether configuration type is required to configure one of api key parameters or not
|
||||||
|
|
||||||
|
|
@ -357,19 +275,19 @@ def fill_up_options_using_configuration_type(options, configuration_type)
|
||||||
# If exists then fill in all required information through api_key_path parameter
|
# If exists then fill in all required information through api_key_path parameter
|
||||||
# and set a value to an options` parameter respectively
|
# and set a value to an options` parameter respectively
|
||||||
|
|
||||||
default_options = {:api_key_path => api_key_path}
|
default_options[:api_key_path] = api_key_path
|
||||||
else
|
else
|
||||||
|
|
||||||
# If doesn't exist then build api_key parameter through app_store_connect_api_key action
|
# 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
|
# and set a value to an options` parameter respectively also
|
||||||
|
|
||||||
default_options = {:api_key => get_app_store_connect_api_key()}
|
default_options[:api_key] = get_app_store_connect_api_key()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
default_options
|
default_options
|
||||||
.merge(configuration.to_options)
|
.merge(configuration.to_options)
|
||||||
.merge(get_keychain_options(options))
|
.merge(get_keychain_options(options, keychain_password_required))
|
||||||
.merge(options)
|
.merge(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -387,15 +305,15 @@ def get_app_store_connect_api_key()
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_keychain_options(options)
|
def get_keychain_options(options, keychain_password_required = false)
|
||||||
keychain_name = options[:keychain_name]
|
keychain_name = options[:keychain_name]
|
||||||
keychain_password = options[:keychain_password]
|
keychain_password = options[:keychain_password]
|
||||||
|
|
||||||
if is_ci?
|
if is_ci?
|
||||||
keychain_name = keychain_name || "ci.keychain"
|
keychain_name = keychain_name || "ci.keychain"
|
||||||
keychain_password = keychain_password || ""
|
keychain_password = keychain_password || ""
|
||||||
else
|
elsif keychain_password_required && keychain_password.nil?
|
||||||
keychain_password = keychain_password || prompt(
|
keychain_password = prompt(
|
||||||
text: "Please enter your keychain password (account password): ",
|
text: "Please enter your keychain password (account password): ",
|
||||||
secure_text: true
|
secure_text: true
|
||||||
)
|
)
|
||||||
|
|
@ -414,34 +332,6 @@ def get_google_services_plist_path(app_target_folder_name, configuration_type)
|
||||||
File.expand_path "../#{app_target_folder_name}/Resources/GoogleService-Info.plist"
|
File.expand_path "../#{app_target_folder_name}/Resources/GoogleService-Info.plist"
|
||||||
end
|
end
|
||||||
|
|
||||||
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)
|
def set_xcconfig_for_configuration_of_project(lane_name, configuration, xcodeproj_path)
|
||||||
require 'xcodeproj'
|
require 'xcodeproj'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit e591d65722d78918c84c4eeda3df8c4012e75323
|
Subproject commit a8f072f216684bd7f5563c0211f71d6637a5f92d
|
||||||
|
|
@ -6,6 +6,8 @@ module Touchlane
|
||||||
class LocalStorage < Match::Storage::Interface
|
class LocalStorage < Match::Storage::Interface
|
||||||
attr_accessor :signing_identities_path
|
attr_accessor :signing_identities_path
|
||||||
|
|
||||||
|
STORAGE_TYPE = "local"
|
||||||
|
|
||||||
def self.configure(params)
|
def self.configure(params)
|
||||||
return self.new(
|
return self.new(
|
||||||
# we can't pass signing_identities_path since params is hardcoded in match/runner.rb
|
# we can't pass signing_identities_path since params is hardcoded in match/runner.rb
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
require 'match'
|
||||||
|
|
||||||
|
# ugly hack to add support for custom storage
|
||||||
|
|
||||||
|
Match.module_eval do
|
||||||
|
def self.storage_modes
|
||||||
|
return ['git', 'google_cloud' 's3', Touchlane::LocalStorage::STORAGE_TYPE]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def register_local_storage_for_match
|
||||||
|
storage_type = Touchlane::LocalStorage::STORAGE_TYPE
|
||||||
|
|
||||||
|
Match::Storage.register_backend(type: storage_type, storage_class: Touchlane::LocalStorage)
|
||||||
|
Match::Encryption.register_backend(type: storage_type, encryption_class: Match::Encryption::OpenSSL)
|
||||||
|
end
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
module Touchlane
|
module Touchlane
|
||||||
require_relative "touchlane/configuration_type"
|
require_relative "touchlane/configuration_type"
|
||||||
require_relative "touchlane/configuration"
|
require_relative "touchlane/configuration"
|
||||||
require_relative "touchlane/features"
|
require_relative "match/storage/local_storage_register"
|
||||||
require_relative "match/storage/local_storage"
|
require_relative "touchlane/actions/sync_signing_identities"
|
||||||
|
require_relative "touchlane/actions/refresh_profiles"
|
||||||
|
require_relative "touchlane/actions/replace_development_certificate"
|
||||||
|
require_relative "touchlane/actions/manually_update_code_signing"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
require 'match'
|
||||||
|
require_relative '../../match/storage/local_storage'
|
||||||
|
|
||||||
|
def manually_update_code_signing(options)
|
||||||
|
register_local_storage_for_match()
|
||||||
|
|
||||||
|
storage_factory = lambda do
|
||||||
|
new_storage = Match::Storage.from_params(options)
|
||||||
|
new_storage.download
|
||||||
|
return new_storage
|
||||||
|
end
|
||||||
|
|
||||||
|
encryption_factory = lambda do |stor|
|
||||||
|
new_encryption = Match::Encryption.for_storage_mode(options[:storage_mode], { working_directory: stor.working_directory })
|
||||||
|
new_encryption.decrypt_files
|
||||||
|
return new_encryption
|
||||||
|
end
|
||||||
|
|
||||||
|
get_all_files = lambda do |stor|
|
||||||
|
Dir[File.join(stor.working_directory, "**", "*.{cer,p12,mobileprovision}")]
|
||||||
|
end
|
||||||
|
|
||||||
|
storage = storage_factory.call
|
||||||
|
encryption = encryption_factory.call(storage)
|
||||||
|
old_files = get_all_files.call(storage)
|
||||||
|
|
||||||
|
sh("open #{storage.working_directory}")
|
||||||
|
|
||||||
|
# we are not using prompt() since it requires non-empty input which is not a case for Enter (\n)
|
||||||
|
puts "Enter any key when you're done"
|
||||||
|
STDIN.gets
|
||||||
|
|
||||||
|
encryption.encrypt_files
|
||||||
|
|
||||||
|
files_to_commit = get_all_files.call(storage)
|
||||||
|
old_directory = storage.working_directory
|
||||||
|
storage.save_changes!(files_to_commit: files_to_commit)
|
||||||
|
|
||||||
|
|
||||||
|
# need to check, because saving changes with delete is another function (update repo if needed)
|
||||||
|
files_diff = old_files - files_to_commit
|
||||||
|
|
||||||
|
# match can not work with both save/delete functionality `You can't provide both files_to_delete and files_to_commit right now`
|
||||||
|
# to avoid this we use storage twice if needed
|
||||||
|
|
||||||
|
if files_diff.length > 0
|
||||||
|
storage = storage_factory.call
|
||||||
|
encryption = encryption_factory.call(storage)
|
||||||
|
|
||||||
|
files_to_delete = files_diff.map do |file|
|
||||||
|
old_file = file
|
||||||
|
old_file.slice! old_directory
|
||||||
|
new_file = File.join(storage.working_directory, old_file)
|
||||||
|
File.delete(new_file) if File.exist?(new_file)
|
||||||
|
file = new_file
|
||||||
|
end
|
||||||
|
|
||||||
|
encryption.encrypt_files
|
||||||
|
storage.save_changes!(files_to_delete: files_to_delete)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
require 'match'
|
||||||
|
require 'fastlane_core'
|
||||||
|
require 'Spaceship'
|
||||||
|
|
||||||
|
def refresh_profiles(options)
|
||||||
|
register_local_storage_for_match()
|
||||||
|
|
||||||
|
profiles_tmp_dir = Dir.mktmpdir
|
||||||
|
|
||||||
|
unless options[:cert_id]
|
||||||
|
cert_type = Match.cert_type_sym(options[:type])
|
||||||
|
|
||||||
|
storage = Match::Storage.from_params(options)
|
||||||
|
storage.download
|
||||||
|
|
||||||
|
output_dir_certs = File.join(storage.prefixed_working_directory, "certs", cert_type.to_s)
|
||||||
|
|
||||||
|
matched_certs = Dir.glob("*.cer", base: output_dir_certs)
|
||||||
|
|
||||||
|
if matched_certs.empty?
|
||||||
|
FastlaneCore::UI.error("Unable to locate certificate to upate profiles")
|
||||||
|
raise "No certificates found at #{output_dir_certs}"
|
||||||
|
end
|
||||||
|
|
||||||
|
if matched_certs.length > 1
|
||||||
|
options[:cert_id] = File.basename(FastlaneCore::UI.select("Please select the certificate", matched_certs), ".cer")
|
||||||
|
else
|
||||||
|
options[:cert_id] = File.basename(matched_certs.first, ".cer")
|
||||||
|
end
|
||||||
|
|
||||||
|
if options[:cert_path].nil? && options[:p12_path].nil?
|
||||||
|
encryption = Match::Encryption.for_storage_mode(options[:storage_mode], { working_directory: storage.working_directory })
|
||||||
|
encryption.decrypt_files
|
||||||
|
|
||||||
|
tmp_certs_dir = Dir.mktmpdir
|
||||||
|
|
||||||
|
cer_file_name = "#{options[:cert_id]}.cer"
|
||||||
|
cert_path = File.join(output_dir_certs, cer_file_name)
|
||||||
|
options[:cert_path] = File.join(tmp_certs_dir, cer_file_name)
|
||||||
|
|
||||||
|
p12_file_name = "#{options[:cert_id]}.p12"
|
||||||
|
p12_path = File.join(output_dir_certs, p12_file_name)
|
||||||
|
options[:p12_path] = File.join(tmp_certs_dir, p12_file_name)
|
||||||
|
|
||||||
|
IO.copy_stream(cert_path, options[:cert_path])
|
||||||
|
IO.copy_stream(p12_path, options[:p12_path])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
app_identifier = options[:app_identifier]
|
||||||
|
|
||||||
|
Spaceship::ConnectAPI.token = Spaceship::ConnectAPI::Token.from_json_file(options[:api_key_path])
|
||||||
|
|
||||||
|
if app_identifier.is_a? Array
|
||||||
|
app_identifier.each { |app_id| refresh_profile_for_app(options, app_id, profiles_tmp_dir) }
|
||||||
|
else
|
||||||
|
refresh_profile_for_app(options, app_identifier, profiles_tmp_dir)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def refresh_profile_for_app(options, app_id, profiles_tmp_dir)
|
||||||
|
provisioning_name = Fastlane::Actions.lane_context[Touchlane::SharedValues::TOUCH_BUNDLE_ID_PROFILE_NAME_MAPPING][app_id]
|
||||||
|
|
||||||
|
profiles = Spaceship::ConnectAPI::Profile.all(filter: { name: provisioning_name })
|
||||||
|
|
||||||
|
if profiles.empty?
|
||||||
|
sigh_for_app(options, app_id, provisioning_name, profiles_tmp_dir)
|
||||||
|
else
|
||||||
|
FastlaneCore::UI.important("Did find existing profile #{provisioning_name}. Removing it from dev portal.")
|
||||||
|
|
||||||
|
profiles.each { |profile| profile.delete! if profile.name == provisioning_name }
|
||||||
|
sigh_for_app(options, app_id, provisioning_name, profiles_tmp_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
Match::Importer.new.import_cert(
|
||||||
|
options,
|
||||||
|
cert_path: options[:cert_path],
|
||||||
|
p12_path: options[:p12_path],
|
||||||
|
profile_path: lane_context[Fastlane::Actions::SharedValues::SIGH_PROFILE_PATH]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def sigh_for_app(options, app_id, provisioning_name, profiles_tmp_dir)
|
||||||
|
sigh(
|
||||||
|
app_identifier: app_id,
|
||||||
|
development: options[:development],
|
||||||
|
username: options[:username] || options[:apple_id],
|
||||||
|
api_key_path: options[:api_key_path],
|
||||||
|
api_key: options[:api_key],
|
||||||
|
team_id: options[:team_id],
|
||||||
|
provisioning_name: provisioning_name,
|
||||||
|
output_path: profiles_tmp_dir,
|
||||||
|
cert_id: options[:cert_id],
|
||||||
|
force: true # will also add all available devices to this profile
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
def replace_development_certificate(options)
|
||||||
|
register_local_storage_for_match()
|
||||||
|
|
||||||
|
certs_path_tmp_dir = Dir.mktmpdir
|
||||||
|
|
||||||
|
cert(
|
||||||
|
development: true,
|
||||||
|
username: options[:username] || options[:apple_id],
|
||||||
|
api_key_path: options[:api_key_path],
|
||||||
|
api_key: options[:api_key],
|
||||||
|
team_id: options[:team_id],
|
||||||
|
output_path: certs_path_tmp_dir,
|
||||||
|
keychain_password: options[:keychain_password]
|
||||||
|
)
|
||||||
|
|
||||||
|
options[:cert_id] = lane_context[Fastlane::Actions::SharedValues::CERT_CERTIFICATE_ID]
|
||||||
|
options[:cert_path] = lane_context[Fastlane::Actions::SharedValues::CERT_FILE_PATH]
|
||||||
|
options[:p12_path] = File.join(File.dirname(options[:cert_path]), "#{options[:cert_id]}.p12")
|
||||||
|
options[:readonly] = false
|
||||||
|
|
||||||
|
refresh_profiles(options)
|
||||||
|
|
||||||
|
sh("open #{certs_path_tmp_dir}")
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
def install_signing_identities(options)
|
||||||
|
readonly_options = options
|
||||||
|
readonly_options[:readonly] = true
|
||||||
|
|
||||||
|
sync_signing_identities(readonly_options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_signing_identities(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: options[:storage_mode],
|
||||||
|
# we can't pass signing_identities_path as parameter name since params is hardcoded in match/runner.rb
|
||||||
|
git_url: options[:signing_identities_path] || options[:git_url],
|
||||||
|
skip_docs: true,
|
||||||
|
keychain_name: options[:keychain_name],
|
||||||
|
keychain_password: options[:keychain_password]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
require "yaml"
|
require "yaml"
|
||||||
|
|
||||||
module Touchlane
|
module Touchlane
|
||||||
|
module SharedValues
|
||||||
|
TOUCH_BUNDLE_ID_PROFILE_NAME_MAPPING = :TOUCH_BUNDLE_ID_PROFILE_NAME_MAPPING
|
||||||
|
end
|
||||||
|
|
||||||
class Configuration
|
class Configuration
|
||||||
def initialize(type, app_identifier, apple_id, team_id, itc_team_id)
|
def initialize(type, app_identifier, apple_id, team_id, itc_team_id)
|
||||||
@type = type
|
@type = type
|
||||||
|
|
@ -13,6 +17,8 @@ module Touchlane
|
||||||
attr_reader :type, :app_identifier, :apple_id, :team_id, :itc_team_id
|
attr_reader :type, :app_identifier, :apple_id, :team_id, :itc_team_id
|
||||||
|
|
||||||
def self.from_file(path, type)
|
def self.from_file(path, type)
|
||||||
|
Fastlane::Actions.lane_context[SharedValues::TOUCH_BUNDLE_ID_PROFILE_NAME_MAPPING] = {}
|
||||||
|
|
||||||
configuration_hash = load_configuration_from_file(path)
|
configuration_hash = load_configuration_from_file(path)
|
||||||
attrs_hash = configuration_hash["types"][type]
|
attrs_hash = configuration_hash["types"][type]
|
||||||
identifiers = get_app_identifiers_from_configuration_hash(configuration_hash, type)
|
identifiers = get_app_identifiers_from_configuration_hash(configuration_hash, type)
|
||||||
|
|
@ -35,8 +41,15 @@ module Touchlane
|
||||||
|
|
||||||
def self.get_app_identifiers_from_configuration_hash(configuration_hash, type)
|
def self.get_app_identifiers_from_configuration_hash(configuration_hash, type)
|
||||||
identifier_key = "PRODUCT_BUNDLE_IDENTIFIER"
|
identifier_key = "PRODUCT_BUNDLE_IDENTIFIER"
|
||||||
|
profile_name_key = "PROVISIONING_PROFILE_SPECIFIER"
|
||||||
|
|
||||||
configuration_hash["targets"].collect do |target, types|
|
configuration_hash["targets"].collect do |target, types|
|
||||||
types[type][identifier_key] or raise "#{target}: There is no #{identifier_key} field in #{type}"
|
bundle_id = types[type][identifier_key]
|
||||||
|
profile_name = types[type][profile_name_key]
|
||||||
|
|
||||||
|
Fastlane::Actions.lane_context[SharedValues::TOUCH_BUNDLE_ID_PROFILE_NAME_MAPPING][bundle_id] = profile_name
|
||||||
|
|
||||||
|
bundle_id or raise "#{target}: There is no #{identifier_key} field in #{type}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@ module Touchlane
|
||||||
DEVELOPMENT = "development"
|
DEVELOPMENT = "development"
|
||||||
ENTERPRISE = "enterprise"
|
ENTERPRISE = "enterprise"
|
||||||
APP_STORE = "appstore"
|
APP_STORE = "appstore"
|
||||||
|
ADHOC = "adhoc"
|
||||||
|
|
||||||
DEVELOPMENT_PREFIX = "Standard"
|
DEVELOPMENT_PREFIX = "Standard"
|
||||||
ENTERPRISE_PREFIX = "Enterprise"
|
ENTERPRISE_PREFIX = "Enterprise"
|
||||||
APP_STORE_PREFIX = "AppStore"
|
APP_STORE_PREFIX = "AppStore"
|
||||||
|
ADHOC_PREFIX = "AdHoc"
|
||||||
|
|
||||||
private_constant :DEVELOPMENT, :ENTERPRISE, :APP_STORE
|
private_constant :DEVELOPMENT, :ENTERPRISE, :APP_STORE, :ADHOC_PREFIX
|
||||||
private_constant :DEVELOPMENT_PREFIX, :ENTERPRISE_PREFIX, :APP_STORE_PREFIX
|
private_constant :DEVELOPMENT_PREFIX, :ENTERPRISE_PREFIX, :APP_STORE_PREFIX, :ADHOC_PREFIX
|
||||||
|
|
||||||
def initialize(type)
|
def initialize(type)
|
||||||
@type = type
|
@type = type
|
||||||
|
|
@ -30,6 +32,10 @@ module Touchlane
|
||||||
@export_method = "app-store"
|
@export_method = "app-store"
|
||||||
@configuration = "AppStore"
|
@configuration = "AppStore"
|
||||||
@prefix = APP_STORE_PREFIX
|
@prefix = APP_STORE_PREFIX
|
||||||
|
when ADHOC
|
||||||
|
@export_method = type
|
||||||
|
@export_method = "ad-hoc"
|
||||||
|
@prefix = ADHOC_PREFIX
|
||||||
else
|
else
|
||||||
raise "Unknown type passed #{type}"
|
raise "Unknown type passed #{type}"
|
||||||
end
|
end
|
||||||
|
|
@ -47,9 +53,11 @@ module Touchlane
|
||||||
from_type(APP_STORE)
|
from_type(APP_STORE)
|
||||||
when lane_name.start_with?(DEVELOPMENT_PREFIX)
|
when lane_name.start_with?(DEVELOPMENT_PREFIX)
|
||||||
from_type(DEVELOPMENT)
|
from_type(DEVELOPMENT)
|
||||||
|
when lane_name.start_with?(ADHOC_PREFIX)
|
||||||
|
from_type(ADHOC)
|
||||||
else
|
else
|
||||||
raise "Unable to map #{lane_name} to #{ConfigurationType.class}."
|
raise "Unable to map #{lane_name} to #{ConfigurationType.class}."
|
||||||
+ "Available prefixes: #{DEVELOPMENT_PREFIX}, #{ENTERPRISE_PREFIX}, #{APP_STORE_PREFIX}"
|
+ "Available prefixes: #{DEVELOPMENT_PREFIX}, #{ENTERPRISE_PREFIX}, #{APP_STORE_PREFIX}, #{ADHOC_PREFIX}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -65,15 +73,18 @@ module Touchlane
|
||||||
from_type(ENTERPRISE)
|
from_type(ENTERPRISE)
|
||||||
when APP_STORE_PREFIX
|
when APP_STORE_PREFIX
|
||||||
from_type(APP_STORE)
|
from_type(APP_STORE)
|
||||||
|
when ADHOC_PREFIX
|
||||||
|
from_type(ADHOC)
|
||||||
else
|
else
|
||||||
raise "Unable to map #{account_type} to #{ConfigurationType.class}."
|
raise "Unable to map #{account_type} to #{ConfigurationType.class}."
|
||||||
+ "Available account types: #{DEVELOPMENT_PREFIX}, #{ENTERPRISE_PREFIX}, #{APP_STORE_PREFIX}"
|
+ "Available account types: #{DEVELOPMENT_PREFIX}, #{ENTERPRISE_PREFIX}, #{APP_STORE_PREFIX}, #{ADHOC_PREFIX}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_options
|
def to_options
|
||||||
{
|
{
|
||||||
:type => @type,
|
:type => @type,
|
||||||
|
:development => @is_development,
|
||||||
:export_method => @export_method,
|
:export_method => @export_method,
|
||||||
:configuration => @configuration
|
:configuration => @configuration
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
module Managers
|
|
||||||
require_relative "lib/file_manager"
|
|
||||||
require_relative "lib/template_manager"
|
|
||||||
end
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
module Templates
|
|
||||||
require_relative "templates/features_templates"
|
|
||||||
end
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
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
|
|
||||||
Loading…
Reference in New Issue