Merge pull request #242 from TouchInstinct/feature/build_phases_script_improvements

Feature/build phases script improvements
This commit is contained in:
Ivan Smolin 2021-03-26 10:54:15 +03:00 committed by GitHub
commit cd606ce17d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 367 additions and 119 deletions

View File

@ -33,6 +33,7 @@ opt_in_rules:
- extension_access_modifier
- explicit_init
- prefer_zero_over_explicit_init
- fallthrough
# style
@ -65,6 +66,7 @@ opt_in_rules:
- identical_operands
- overridden_super_call
- unowned_variable_capture
- comment_spacing
# metrics
@ -75,6 +77,7 @@ excluded:
- Pods
- Generated
- "**/Generated"
- "**/Resources"
line_length:
warning: 128
@ -187,26 +190,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"
@ -214,75 +203,61 @@ 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
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
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: '\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: error
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: error
severity: warning
pattern_matching:
name: "Pattern matching"

141
xcode/build_phases/api_generator.sh Normal file → Executable file
View File

@ -1,9 +1,144 @@
#!/bin/sh
# Description:
# Generates API models & methods.
#
# Parameters:
# $1 - api generator version.
#
# Optional environment variables:
# OUTPUT_PATH - path to Generated folder.
#
# Examples of usage:
# . api_generator.sh 1.4.0-beta1
# . api_generator.sh 1.4.0-beta1 ${TARGET_NAME}/Generated
#
readonly EXIT_SUCCESS=0
readonly EXIT_FAILURE=1
readonly TRUE=0
readonly FALSE=1
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}
}
get_current_commit()
{
if [ -z "${CURRENT_COMMIT}" ]; then
if [ -z "${REPO_PATH}" ]; 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 ${REPO_PATH} rev-parse --verify HEAD`
fi
else
echo ${CURRENT_COMMIT}
fi
}
is_nothing_changed_since_last_check()
{
if is_force_run; then
echo "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
echo "COMMIT_FILE_PATH should be defined or passed as first argument!"
return ${EXIT_FAILURE}
fi
fi
if [ -z "${2}" ]; then
local -r CURRENT_COMMIT=`get_current_commit`
else
local -r CURRENT_COMMIT=${2}
fi
local -r LAST_CHECKED_COMMIT=`cat ${COMMIT_FILE_PATH}` || ""
if [ ${CURRENT_COMMIT} = "${LAST_CHECKED_COMMIT}" ]; then
return ${EXIT_SUCCESS}
else
return ${EXIT_FAILURE}
fi
}
record_current_commit()
{
if is_force_run; then
echo "Force run detected. Commit won't be recorder."
exit ${EXIT_SUCCESS}
fi
if [ -z "${1}" ]; then
local -r CURRENT_COMMIT=`get_current_commit`
else
local -r CURRENT_COMMIT=${1}
fi
if [ -z "${COMMIT_FILE_PATH}" ]; then
if [ ! -v "${2}" ]; then
local -r COMMIT_FILE_PATH=${2}
else
echo "COMMIT_FILE_PATH should be defined or passed as second argument!"
return ${EXIT_FAILURE}
fi
fi
echo ${CURRENT_COMMIT} > ${COMMIT_FILE_PATH}
}
readonly BUILD_PHASES_DIR=${SRCROOT}/build_phases
mkdir -p ${BUILD_PHASES_DIR}
readonly COMMIT_FILE_PATH=${BUILD_PHASES_DIR}/api-generator-commit
readonly REPO_PATH="common"
if is_nothing_changed_since_last_check; then
echo "Nothing was changed models generation skipped."
exit ${EXIT_SUCCESS}
fi
VERSION=$1
FILE_NAME="api-generator-${VERSION}.jar"
if [ -z "${OUTPUT_PATH}" ]; then
if [ ! -z "${2}" ]; then
readonly OUTPUT_PATH=${2}
else
readonly OUTPUT_PATH="Generated"
fi
fi
mkdir -p ${OUTPUT_PATH}
# download api generator
link="https://dl.bintray.com/touchin/touchin-tools/ru/touchin/api-generator/${VERSION}/${FILE_NAME}"
. build-scripts/xcode/aux_scripts/download_file.sh ${FILE_NAME} ${link}
readonly DOWNLOAD_URL="https://dl.bintray.com/touchin/touchin-tools/ru/touchin/api-generator/${VERSION}/${FILE_NAME}"
. build-scripts/xcode/aux_scripts/download_file.sh ${FILE_NAME} ${DOWNLOAD_URL}
# 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
java -Xmx6g -jar "Downloads/${FILE_NAME}" generate-client-code --output-language SWIFT --specification-path common/api --output-path ${OUTPUT_PATH} --single-file true
record_current_commit

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

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

@ -1,20 +1,59 @@
if which pmd >/dev/null; then
readonly CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
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 " " -`
#!/bin/sh
mkdir ${REPORTS_DIR}
# 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
#
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 EXIT_SUCCESS=0
readonly EXIT_FAILURE=1
php ${CURRENT_DIR}/../aux_scripts/cpd_script.php ${REPORTS_DIR}/cpd-output.xml | tee ${REPORTS_DIR}/CPDLog.txt
if which /usr/local/bin/pmd >/dev/null; then
readonly REPORTS_DIR="${PROJECT_DIR}/code-quality-reports"
# Make paths relative to SOURCES_DIR, so different developers won't rewrite entire file
readonly SED_REPLACEMENT_STRING=$(echo ${SOURCES_DIR} | sed "s/\//\\\\\//g")
readonly SOURCES_DIRS=`. ${SCRIPT_DIR}/common/read_input_file_names.sh " " ${PROJECT_DIR}`
readonly COMMAND_LINE_ARGUMENTS=$@
FOLDERS_TO_EXLUDE=""
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}
/usr/local/bin/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

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

@ -1,2 +1,47 @@
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.
#
# Example of usage:
# 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
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

View File

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

View File

@ -23,42 +23,12 @@ private_lane :installDependencies do |options|
cocoapods(
repo_update: 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])
@ -70,8 +40,7 @@ private_lane :uploadToFirebase do |options|
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")
release_notes_file: releaseNotesFile
)
upload_symbols_to_crashlytics(
@ -139,6 +108,7 @@ private_lane :buildConfiguration do |options|
installDependencies(options)
run_code_generation_phase_if_needed(options)
generate_enabled_features_extension_if_needed(options)
if !(options[:uploadToFabric] || options[:uploadToAppStore])
@ -465,3 +435,14 @@ def set_xcconfig_for_configuration_of_project(lane_name, configuration, xcodepro
project.save()
end
# Build phases
def run_code_generation_phase_if_needed(options)
code_generation_script_path = File.expand_path "../.githooks/scripts/CodeGen.sh"
xcodeproj_path = File.expand_path options[:xcodeproj_path]
if File.exists? code_generation_script_path
sh(code_generation_script_path, xcodeproj_path)
end
end

View File

@ -75,27 +75,11 @@ def generate_provisioning_profile(provisioning_key, bundle_id, distribution_type
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)
@ -111,10 +95,6 @@ def generate_missing_properties(target_name, properties, distribution_type)
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