Как переименовать проект в android studio
Перейти к содержимому

Как переименовать проект в android studio

  • автор:

Как переименовать копию проекта в Android Studio?

11:40:58 Unsupported Modules Detected: Compilation is not supported for following modules: H0001 Practise. Unfortunately you can’t have non-Gradle Java modules and Android-Gradle modules in one project.

11:40:58 Gradle sync started

11:41:02 Gradle sync completed

11:41:02 Executing tasks: [:app:generateDebugSources, :app:generateDebugAndroidTestSources]

11:41:03 Gradle build finished in 1s 579ms

11:41:25 Throwable: Merged rootsChanged not allowed inside rootsChanged, rootsChanged level == 1

Как переименовать копию проекта в Android Studio?

На английском стэке задавали тот же вопрос, последовательность действий была примерно следующая:
1. Создать копию проекта используя файловый менеджер.
2. Refactor->Rename для проекта.
3. Изменить Gradle соответствующим образом.

Мои проблемы:
1. Как и что нужно сделать в Gradle?
2. На этапе переименования через Refactor выводит в EventLog следующее:

11:40:58 Unsupported Modules Detected: Compilation is not supported for following modules: H0001 Practise. Unfortunately you can't have non-Gradle Java modules and Android-Gradle modules in one project. 11:40:58 Gradle sync started 11:41:02 Gradle sync completed 11:41:02 Executing tasks: [:app:generateDebugSources, :app:generateDebugAndroidTestSources] 11:41:03 Gradle build finished in 1s 579ms 11:41:25 Throwable: Merged rootsChanged not allowed inside rootsChanged, rootsChanged level == 1
  • Вопрос задан более трёх лет назад
  • 624 просмотра

Глобальный ренейминг в Android-проекте

Привет-привет! Не будем мять бока и начнем максимально быстро.

Но для начала представлюсь. Меня зовут Таскаев Евгений — я Android-разработчик в фичевой команде hh.ru. Пилю всякие интересные фичи, которыми вы пользуетесь каждый день*.

В статье я расскажу, как у нас в Android-приложении прошел глобальный ренейминг фичей и пакетов и их структуризация. Что у нас получилось, а что нет. А стоит ли вам делать так же, вы решите уже сами.

* — если вы каждый день открываете приложение «hh работа»

Для тех, кто не любит читать

У меня для вас есть выход! Эту статью можно глянуть в формате видео на ютубе в одном из эпизодов «Охэхэнных историй» 😉

Представим, что сейчас 2018 год. Представили? А теперь перестаньте плакать. В 2018 тоже было полно проблем.

У нас есть шикарный проект, в котором, по законам жанра, огромный монолит с 1 app gradle модулем и весь код в активити и несколько флэйворов. Основные из них — applicant и employer приложения соискателя и работодателя соответственно.

Структура проекта

В этом нет ничего страшного ровно до тех пор

  • пока команда не слишком разрослась и не стало трудно пилить фичи
  • пока старые разработчики не ушли, а новые знают не всё
  • пока не пришел хайптрейн с многомодульностью
  • пока рак на горе не свистнул

В нашем случае произошло так: были разного рода лики и баги, фиксить которые было сложно, к тому же старые разработчики разбежались, а новым было трудно ориентироваться в том, что осталось. Из-за этого было трудно декомпозировать задачи и разработка стала “дорогой”.

А какие у вас были проблемы на проектах пишите в комментариях)

Структура проекта до ренейминга

Спустя некоторое время, когда многое было зарефачено и отлажено, мы пришли к новому виду структуры проекта. Это как раз та структура, которая была у нас до недавнего времени.

За всё это время в плане структуры произошло следующее:

  • мы избавились от flavors и разделили приложения на два app gradle-модуля — модули которые зависят от плагина «com.android.application».
  • разбили монолит на отдельные library gradle-модули (и в дальнейшем просто gradle-модули) — модули которые зависят от плагина «com.android.library».

Также прошло несколько этапов формирования структуры. И перед последним, о котором я расскажу в конце, структура была такая:

  1. Часть фичей лежали в папке feature в корне проекта, туда складывались gradle-модули фичей соискательского приложения с префиксом “feature-” в именовании.
    Например, фича резюме называлась feature-resume. Это рудиментарное решение, которое появилось почти в самом начале рефакторинга.
  2. Некоторые фичи состояли из нескольких сабфичей, по аналогии с 1 пунктом, но создавался не gradle-модуль, а папка также с префиксом, куда уже складывались нужные фичи. Слишком сложно? Сейчас приведу пример. Фича резюме, feature-resume, внутри нее лежали gradle-модули: feature-resume-profile,feature-resume-network и тд.
  3. Общие gradle-модули, которые использовались в обоих приложениях, лежали в папочке shared. Помимо этого, фичи внутри shared делились на core и feature. Соответственно, в какой папке лежал gradle-модуль, то имя папки добавлялось префиксом к названию модуля. Как можете заметить на картинке, с core что-то было не так. По правилам префикс у названия фичей должен быть “shared-core-”.
  4. Основная логика приложений лежала в папках соответствующих названиям приложений applicant (соискательское) и employer (работодательское).
    Структура была аналогична папке shared и фичи в них именовались так:

Проблемы с именованием

  1. Расположение модулей не мэтчилось с их названием, что затрудняло подключение модулей, а иногда и путало, особенно новых разработчиков
  2. Бардак в названии пакетов, конечно были какие то правила, но за 3 года, они тоже менялись, а пакеты могли называться по-разному, иногда даже совпадали у некоторых разных фич

И выход из всего этого был — РЕНЕЙМИНГ!

Ретроспектива ренейминга

А теперь самое интересное. Расскажу про новую структуру проекта.

Начнем с небольшой ретроспективы и разберемся, с какими проблемами мы столкнулись.

До начала рефакторинга мы определились какой хотим видеть новую структуру.

Во время праздничных выходных в феврале мы начали переносить файлы по новым папкам и менять имена пакетов с помощью разного рода скриптов.

Время было выбрано специально, чтобы никто параллельно ничего не делал.Но работа затянулась. Тогда мы плохо понимали масштаб возложенной на нас задачи.

И поэтому решили для начала вручную составить гигантский чеклист того, что предстоит сделать, формата «как модуль назывался -> как будет называться». А также за одно переименовать пакеты в соответсвии с названием модулей

Он выглядел примерно так

#### feature-chat --> chat #### feature-chat/core - [x] applicant-feature-chat-core-data — [x] rename module: applicant-feature-chat-core-data -> :applicant:feature:chat:core:data — [x] rename package: ru.hh.applicant.feature.chat.screen_core_data -> ru.hh.applicant.feature.chat.core.data - [x] applicant-feature-chat-core-network — [x] rename module: applicant-feature-chat-core-network -> :applicant:feature:chat:core:network — [x] rename package: ru.hh.feature_chat_network -> ru.hh.applicant.feature.chat.core.network #### feature-chat/feature - [x] applicant-feature-chat-list — [x] rename module: applicant-feature-chat-list -> :applicant:feature:chat:chat-list — [x] rename package: ru.hh.feature_chat_list -> ru.hh.applicant.feature.chat.list и так далее

И даже после этого мы не до конца осознали, что нора, в которую мы залезли — не кроличья.

Поэтому первые модули было решено перенести просто руками, в Android Studio. После 10 модулей стало понятно, что git-история этих файлов исчезла. Терять историю нам однозначно не хотелось, поэтому мы вспомнили про такую команду как git mv, которая позволяет без потери истории перенести файлы из одной папки в другую.

Попытавшись перенести несколько модулей с использованием git mv, мы сильно взгрустнули. Потому что писать руками эти команды было очень долго и муторно.

Нужно было искать какое-то автоматическое решение. Поэтому мы написали простой bash-скрипт, который для двух заданных папок генерил тонну команд git mv, которые можно было скопировать и сразу запустить.

А вот и сам скриптик на генерацию git mv команд

#!/bin/bash readonly LOCAL_PATH=$1 readonly START_FOLDER=$2 readonly START_PACKAGE=$3 readonly TARGET_FOLDER=$4 readonly TARGET_PACKAGE_NAME="$5" echo "===== MV COMMANDS GENERATOR =====" echo "LOCAL_PATH: $" echo "START_FOLDER: $" echo "START_PACKAGE: $" echo "TARGET_FOLDER: $" echo "TARGET_PACKAGE_NAME: $" readonly SPLITTED_TARGET_PACKAGE=($) TARGET_PATH_PARTS="" for package_part in "$"; do TARGET_PATH_PARTS="$/$" done readonly SOURCE_SET_FOLDER="/src/main/java" readonly SOURCE_KOTLIN_SET_FOLDER="/src/main/kotlin" readonly DESIRED_CODE_ROOT_FOLDER="$$$" echo "DESIRED_CODE_ROOT_FOLDER: $" readonly SPLITTED_START_PACKAGE=($) START_PATH_PARTS="" for package_part in "$"; do START_PATH_PARTS="$/$" done START_CODE_ROOT_FOLDER="$$$" if [ ! -d "$" ]; then echo ". there is no /java source set --> /kotlin source set exists" START_CODE_ROOT_FOLDER="$$$" fi readonly FULL_START_PATH="$(cd "$(dirname "$")"; pwd)/$(basename "$")" echo "START_CODE_ROOT_FOLDER: $" echo "FULL_START_PATH: $" echo "" echo "======= Generation result ======" echo "" # Первая команда — создаём директорию для переноса кода echo "mkdir -p $ && \\" # Перечисляем команды для аккуратного переноса кода for code_directory in $/* ; do NAME="$" echo "git mv $ $ && \\" done # Последняя команда — перенос кода в target_folder echo "git mv $ $" echo "" echo "=================================" echo ""

Дело пошло чуть веселее. Но перенос папок — это всего лишь одна часть истории. Вторая часть заключалась в том, что мы, помимо простого переноса папок, захотели ещё и ПЕРЕИМЕНОВАТЬ некоторые модули, о чем я писал выше, добавив структуры не только в иерархию папок, но и в иерархию package-ей.

Чтобы было вот так:

ru.hh.feature_chat_list -> ru.hh.applicant.feature.chat.list ru.hh.feature_chat_network -> ru.hh.applicant.feature.chat.core.network ru.hh.applicant.feature.chat.screen_core_data -> ru.hh.applicant.feature.chat.core.data

Поэтому помимо генерации команд git mv, нужно было ещё сгенерить команды для переименования одних package-ей в другие. Для этого мы тоже написали дополнительный скрипт, который генерил команды для вызова скрипта переименования.

Скрип запуска скрипта переименования

readonly START_FOLDER=$1 readonly START_PACKAGE=$2 readonly REPLACE_PACKAGE=$3 echo "START_FOLDER = $" echo "START_PACKAGE = $" echo "REPLACE_PACKAGE = $" echo "" echo "=========" echo "" for filename in $/*; do withoutPath=$(basename -- "$filename") fff="$" echo "sh global_rename.sh $.$ $.$ && \\" done echo "" echo "======== spoiler">Сам скрипт переименования пакетов
#!/bin/bash readonly OLD_PACKAGE=$1 readonly NEW_PACKAGE=$2 find . -type d \( \ -name 'firebase' -o \ -name 'gradle' -o \ -name 'hooks' -o \ -name 'lint' -o \ -name 'profiling' -o \ -name 'scripts' -o \ -name 'detekt' -o \ -name '.git' -o \ -name '.gradle' -o \ -name '.mainframer' -o \ -name 'build' -o \ -name '.idea' -o \ -name 'android-style-guide' -o \ -name 'ci' \ \) -prune -o \ -type f \( \ -name '*.java' -o \ -name '*.kt' -o \ -name '*.gradle' -o \ -name '*.xml' -o \ -name '*.txt' \ \) \ -print0 | xargs -0 sed -i '' "s/$/$/g" echo "done rename for ( $ / $ )"

В итоге мы просидели все выходные, по очереди генерируя команды для каждого модуля и проверяя, собирается ли приложение и запускается ли оно вообще.
Так прошло 3 дня и две ночи.

Ответственные за это люди ушли отдыхать, а остальные разработчики продолжили, но у нас ничего не вышло.

А не получилось потому, что:

  • в больших фиче ветках разработчиков было много изменений и таких веток было несколько, если бы каждый мержил себе сам, то он с большей вероятностью кто-то мог ошибиться где-то
  • также некоторые ветки пересекались по изменениям и было трудно потом все это смержить еще и с ренеймингом

Подумав, мы решили что было бы хорошо, если кто-то один замержит ренейминг везде!

Поэтому решили фиче ветки смержить сразу в develop, а те кто не хочет мержить сейчас, а хотят еще поработать, будут потом сами разруливать конфликты с новым develop.

И мы не до конца понимали, чем нам это грозит.

Смержив все, что можно было в develop, в пятницу мы объявили кодфриз и избранный занялся мержем ренейминга в develop.

За неделю работы команд накопилось кучу изменений, и при мерже вылезло много конфликтов.

Это малая часть конфликтов

После мержа ветки с ренеймингом в старый develop (недельной давности), старые фичи сменили имя и пакет, по факту это означало, что они отправились в другую папку.

Обратите внимание на файлы, все хорошо, они лежат в новом месте.

Но при мерже ренейминга в новый develop после недельной работы, появились такие фантомные структуры:

По старым путям пакетов лежали файлы, которые были изменены разработчиками, но изменены они были в старой структуре, и таких мест было много. голова шла кругом.

Естественно, все это править руками будет долго. Чтобы ускорить процесс, в качестве вспомогательного инструмента, была выбрана утилита rsync, потому что она умеет рекурсивно мержить папки друг с другом, и ей можно указать стратегию разрешения конфликтов (перезапись, оставить новое, оставить старое, etc.).
В консоли с ее помощью рекурсивно переносили папки фичей. Из папки со старым названием в папку с новым.

Затем с помощью волшебных настроек Android Studio — Optimize imports on fly и Add unambiguous imports on the fly, были поправлены проблемы с импортами. Да, мы заходили ручками в каждый файл.

Настройки Android Studio

По-хорошему, надо было идти сначала от корневых модулей (shared/core) и делать регулярный синк проекта в IDE. Так пришлось бы гораздо меньше страдать потом с импортами при переносах файлов, ибо Android Studio автоматически бы их сразу переименовывала во всех местах, куда дотянется.

Но эта мысль пришла в наши светлые головы уже после проделанной работы и полученного опыта.

Спустя пару дней мучений мержа, develop был актуализирован и содержал новую структуру папок и новые имена пакетов.

А ребятам, которые не стали мержить свои ветки в develop, была предоставлена инструкция, как безболезненно влить в себе новый develop.

Но все было не так просто, как хотелось бы.

Приведу список основных пунктов, если вдруг вам понадобится:

  1. Вмержить к себе в ветку develop и сохранить лог конфликтов, на память чтобы понимать поле работы
  2. Конфликты типа modified — modified разрешить самим, руками, но таких конфликтов было минимум
  3. Остальные конфликты нужно разрешить в свою пользу
  4. Нужно перевести все созданные вами папки на новую структуру
  5. Каждый перенос лучше делать отдельным коммитом, чтобы ничего не потерять и чтобы лучше контролировать процесс
  6. Разрулить силами Android Studio все неправильные импорты
  7. Удалить все неиспользуемые папки

В теории звучит просто, но в реальных условиях:

  • если переносить модуль/модули у себя в ветке, то рефакторинг будет применен к модулю, который располагался в старом месте. Приходилось повторить все, что уже сделал, но для перенесенного модуля (получилась двойная работа) + удалить старый модуль, причем сделать аккуратно, чтобы осталась история гита
  • если переносить файлы у себя в ветке, то рефакторинг будет применен к старым файлам, естественно появлялась неактуальная фантомная структура файлов, которые были уже перенесены, поэтому приходилось аккуратно их объединить с новыми

Всего файлов с конфликтами было ~500 в ~50 модулях.

Итоги ренейминга

  1. Названия всех модулей соответствуют структуре папок.
    И теперь включать gradle-модуль в settings.gradle можно через
    include(':shared:feature:location'), поскольку путь совпадает с неймингом.

Для примера, раньше это делалось вот так:

  • Все модули из папки feature из корня проекта (о чем я писал вначале), переместились на свои законные места в папку applicant/feature.
  • Имена пакетов стали соотноситься с расположением фичи.
    Например, фича геолокации :shared:feature:location получила пакет ru.hh.shared.feature.location.
  • Gradle-модули избавились от префиксов feature, shared, core и т.д.
    Но префиксы сабфичей решено было оставить. А потом и их решили не писать 🙂
  • Появилась возможность статической валидация подключения модулей.
    В будущем будем проверять нейминг модулей и пакетов, а также корректность связей между модулями разных типов.
  • Наши рекомендации

    Прежде чем идти в эту историю, надо написать скрипты, которые автоматизируют большую часть работы. Можно воспользоваться нашими наработками, но сначала нужно проверить их на валидность вашему проекту.

    И основной совет — не делайте руками, делайте сразу через скрипты. Это сэкономит кучу нервов и времени.

    Также нужно составить чеклист переноса модулей/файлов. И после каждого этапа переноса по чеклисту нужно проверять: собирается ли проект. Да, это долго и замедляет процесс, но сильно упрощает жизнь в дальнейшем. По крайней мере будет понимание, что “вот из-за этого у меня проект развалился“.

    Для подобных глобальных изменений нужно обязательно уведомлять всю команду и заранее договариваться, как будет идти разработка в это время, чтобы минимизировать конфликты. Самый радикальный инструмент для этого — фичефриз/кодфриз etc. Если вы проводите такой рефакторинг, поддерживайте регулярную связь с командой, сразу же сообщайте о проблемах и потенциально сложных для мерджа местах.

    И не стоит недооценивать эту задачу, если у вас большая кодовая база. Она стопудово займёт больше времени, чем вам кажется.

    На этом все, если у вас остались какие-либо вопросы или вы можете поделиться собственным опытом, пишите в комментариях.

    Спасибо за внимание 😉

    Маленький опрос для большого исследования

    Мы проводим ежегодное исследование технобренда крупнейших IT-компаний России. Нам очень нужно знать, что вы думаете про популярных (и не очень) работодателей. Опрос займет всего 10 минут.

    Пройти опрос можно здесь.

    Как переименовать проект в Android Studio

    Чтобы переименовать название проекта в Android Studio открываем проект и нажимаем на Show Options Menu (шестеренка) и снимаем галочку с Compact Middle Packages. Таким образом разбиваем названия через точку на отдельные каталоги. Например, com.domain.application разбивается на каталог com в котором каталог domain и далее каталог application. По сути получается наименование сайта, только наоборот.

    Show Options Menu

    Затем выбираем каталог для переименования и правой клавишей мыши открываем Refactor->Rename

    refactor

    Появится окно предупреждения, жмем Rename Package

    rename package

    Указываем новое название каталога

    rename

    Далее снизу жмем Do Refactor

    do refactor

    После того, как переименовали файлы, возможно где-то в файлах остались старые названия приложения, например, com.domain.application. В Android Studio нажимаем комбинаций клавиш для поиска ctrl+shift+F. Отобразятся файлы, например, BuildConfig.java, Activity.java, Build.Gradle и другие, в которых есть старые названия, заменяем их на новые.

    3335

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *