Skip to content
代码片段 群组 项目
提交 3288505d 编辑于 作者: Michael Kozono's avatar Michael Kozono
浏览文件

Merge branch 'detect-android-projects' into 'master'

Detect Android projects

See merge request gitlab-org/gitlab!85681
No related branches found
No related tags found
无相关合并请求
# frozen_string_literal: true
class ProjectSetting < ApplicationRecord
ALLOWED_TARGET_PLATFORMS = %w(ios osx tvos watchos).freeze
ALLOWED_TARGET_PLATFORMS = %w(ios osx tvos watchos android).freeze
belongs_to :project, inverse_of: :project_setting
......
# frozen_string_literal: true
module Projects
# Service class to detect if a project is made to run on the Android platform.
#
# This service searches for an AndroidManifest.xml file which all Android app
# project must have. It returns the symbol :android if the given project is an
# Android app project.
#
# Ref: https://developer.android.com/guide/topics/manifest/manifest-intro
#
# Example usage:
# > AndroidTargetPlatformDetectorService.new(a_project).execute
# => nil
# > AndroidTargetPlatformDetectorService.new(an_android_project).execute
# => :android
class AndroidTargetPlatformDetectorService < BaseService
# <manifest> element is required and must occur once inside AndroidManifest.xml
MANIFEST_FILE_SEARCH_QUERY = '<manifest filename:AndroidManifest.xml'
def execute
detect
end
private
def file_finder
@file_finder ||= ::Gitlab::FileFinder.new(project, project.default_branch)
end
def detect
return :android if file_finder.find(MANIFEST_FILE_SEARCH_QUERY).present?
end
end
end
......@@ -4,15 +4,22 @@ module Projects
class RecordTargetPlatformsService < BaseService
include Gitlab::Utils::StrongMemoize
def initialize(project, detector_service)
@project = project
@detector_service = detector_service
end
def execute
record_target_platforms
end
private
attr_reader :project, :detector_service
def target_platforms
strong_memoize(:target_platforms) do
AppleTargetPlatformDetectorService.new(project).execute
Array(detector_service.new(project).execute)
end
end
......@@ -36,6 +43,7 @@ def experiment_candidate?
end
def send_build_ios_app_guide_email
return unless target_platforms.include? :ios
return unless experiment_candidate?
campaign = Users::InProductMarketingEmail::BUILD_IOS_APP_GUIDE
......
......@@ -7,6 +7,7 @@ class RecordTargetPlatformsWorker
LEASE_TIMEOUT = 1.hour.to_i
APPLE_PLATFORM_LANGUAGES = %w(swift objective-c).freeze
ANDROID_PLATFORM_LANGUAGES = %w(java kotlin).freeze
feature_category :experimentation_activation
data_consistency :always
......@@ -18,10 +19,10 @@ def perform(project_id)
@project = Project.find_by_id(project_id)
return unless project
return unless uses_apple_platform_languages?
return unless detector_service
try_obtain_lease do
@target_platforms = Projects::RecordTargetPlatformsService.new(project).execute
@target_platforms = Projects::RecordTargetPlatformsService.new(project, detector_service).execute
log_target_platforms_metadata
end
end
......@@ -30,8 +31,29 @@ def perform(project_id)
attr_reader :target_platforms, :project
def detector_service
if uses_apple_platform_languages?
AppleTargetPlatformDetectorService
elsif uses_android_platform_languages? && detect_android_projects_enabled?
AndroidTargetPlatformDetectorService
end
end
def detect_android_projects_enabled?
Feature.enabled?(:detect_android_projects, project)
end
def uses_apple_platform_languages?
project.repository_languages.with_programming_language(*APPLE_PLATFORM_LANGUAGES).present?
target_languages.with_programming_language(*APPLE_PLATFORM_LANGUAGES).present?
end
def uses_android_platform_languages?
target_languages.with_programming_language(*ANDROID_PLATFORM_LANGUAGES).present?
end
def target_languages
languages = APPLE_PLATFORM_LANGUAGES + ANDROID_PLATFORM_LANGUAGES
@target_languages ||= project.repository_languages.with_programming_language(*languages)
end
def log_target_platforms_metadata
......
---
name: detect_android_projects
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85681
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/360902
milestone: '15.0'
type: development
group: group::activation
default_enabled: false
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::AndroidTargetPlatformDetectorService do
let_it_be(:project) { build(:project) }
subject { described_class.new(project).execute }
before do
allow(Gitlab::FileFinder).to receive(:new) { finder }
end
context 'when project is not an Android project' do
let(:finder) { instance_double(Gitlab::FileFinder, find: []) }
it { is_expected.to be_nil }
end
context 'when project is an Android project' do
let(:finder) { instance_double(Gitlab::FileFinder) }
before do
query = described_class::MANIFEST_FILE_SEARCH_QUERY
allow(finder).to receive(:find).with(query) { [instance_double(Gitlab::Search::FoundBlob)] }
end
it { is_expected.to eq :android }
end
end
......@@ -5,25 +5,38 @@
RSpec.describe Projects::RecordTargetPlatformsService, '#execute' do
let_it_be(:project) { create(:project) }
subject(:execute) { described_class.new(project).execute }
let(:detector_service) { Projects::AppleTargetPlatformDetectorService }
context 'when project is an XCode project' do
def project_setting
ProjectSetting.find_by_project_id(project.id)
end
subject(:execute) { described_class.new(project, detector_service).execute }
context 'when detector returns target platform values' do
let(:detector_result) { [:ios, :osx] }
let(:service_result) { detector_result.map(&:to_s) }
before do
double = instance_double(Projects::AppleTargetPlatformDetectorService, execute: [:ios, :osx])
allow(Projects::AppleTargetPlatformDetectorService).to receive(:new) { double }
double = instance_double(detector_service, execute: detector_result)
allow(detector_service).to receive(:new) { double }
end
it 'creates a new setting record for the project', :aggregate_failures do
expect { execute }.to change { ProjectSetting.count }.from(0).to(1)
expect(ProjectSetting.last.target_platforms).to match_array(%w(ios osx))
shared_examples 'saves and returns detected target platforms' do
it 'creates a new setting record for the project', :aggregate_failures do
expect { execute }.to change { ProjectSetting.count }.from(0).to(1)
expect(ProjectSetting.last.target_platforms).to match_array(service_result)
end
it 'returns the array of stored target platforms' do
expect(execute).to match_array service_result
end
end
it 'returns array of detected target platforms' do
expect(execute).to match_array %w(ios osx)
it_behaves_like 'saves and returns detected target platforms'
context 'when detector returns a non-array value' do
let(:detector_service) { Projects::AndroidTargetPlatformDetectorService }
let(:detector_result) { :android }
let(:service_result) { [detector_result.to_s] }
it_behaves_like 'saves and returns detected target platforms'
end
context 'when a project has an existing setting record' do
......@@ -31,6 +44,10 @@ def project_setting
create(:project_setting, project: project, target_platforms: saved_target_platforms)
end
def project_setting
ProjectSetting.find_by_project_id(project.id)
end
context 'when target platforms changed' do
let(:saved_target_platforms) { %w(tvos) }
......@@ -81,23 +98,44 @@ def project_setting
it_behaves_like 'tracks experiment assignment event'
end
shared_examples 'does not send email' do
it 'does not execute a Projects::InProductMarketingCampaignEmailsService' do
expect(Projects::InProductMarketingCampaignEmailsService).not_to receive(:new)
execute
end
end
context 'experiment control' do
before do
stub_experiments(build_ios_app_guide_email: :control)
end
it 'does not execute a Projects::InProductMarketingCampaignEmailsService' do
expect(Projects::InProductMarketingCampaignEmailsService).not_to receive(:new)
it_behaves_like 'does not send email'
it_behaves_like 'tracks experiment assignment event'
end
execute
context 'when project is not an iOS project' do
let(:detector_service) { Projects::AppleTargetPlatformDetectorService }
let(:detector_result) { :android }
before do
stub_experiments(build_ios_app_guide_email: :candidate)
end
it_behaves_like 'tracks experiment assignment event'
it_behaves_like 'does not send email'
it 'does not track experiment assignment event', :experiment do
expect(experiment(:build_ios_app_guide_email))
.not_to track(:assignment)
execute
end
end
end
end
context 'when project is not an XCode project' do
context 'when detector does not return any target platform values' do
before do
double = instance_double(Projects::AppleTargetPlatformDetectorService, execute: [])
allow(Projects::AppleTargetPlatformDetectorService).to receive(:new).with(project) { double }
......
......@@ -7,11 +7,11 @@
let_it_be(:swift) { create(:programming_language, name: 'Swift') }
let_it_be(:objective_c) { create(:programming_language, name: 'Objective-C') }
let_it_be(:java) { create(:programming_language, name: 'Java') }
let_it_be(:kotlin) { create(:programming_language, name: 'Kotlin') }
let_it_be(:project) { create(:project, :repository, detected_repository_languages: true) }
let(:worker) { described_class.new }
let(:service_result) { %w(ios osx watchos) }
let(:service_double) { instance_double(Projects::RecordTargetPlatformsService, execute: service_result) }
let(:lease_key) { "#{described_class.name.underscore}:#{project.id}" }
let(:lease_timeout) { described_class::LEASE_TIMEOUT }
......@@ -21,16 +21,20 @@
stub_exclusive_lease(lease_key, timeout: lease_timeout)
end
shared_examples 'performs detection' do
it 'creates and executes a Projects::RecordTargetPlatformService instance for the project', :aggregate_failures do
expect(Projects::RecordTargetPlatformsService).to receive(:new).with(project) { service_double }
shared_examples 'performs detection' do |detector_service_class|
let(:service_double) { instance_double(detector_service_class, execute: service_result) }
it "creates and executes a #{detector_service_class} instance for the project", :aggregate_failures do
expect(Projects::RecordTargetPlatformsService).to receive(:new)
.with(project, detector_service_class) { service_double }
expect(service_double).to receive(:execute)
perform
end
it 'logs extra metadata on done', :aggregate_failures do
expect(Projects::RecordTargetPlatformsService).to receive(:new).with(project) { service_double }
expect(Projects::RecordTargetPlatformsService).to receive(:new)
.with(project, detector_service_class) { service_double }
expect(worker).to receive(:log_extra_metadata_on_done).with(:target_platforms, service_result)
perform
......@@ -45,19 +49,68 @@
end
end
context 'when project uses Swift programming language' do
let!(:repository_language) { create(:repository_language, project: project, programming_language: swift) }
def create_language(language)
create(:repository_language, project: project, programming_language: language)
end
context 'when project uses programming language for Apple platform' do
let(:service_result) { %w(ios osx watchos) }
context 'when project uses Swift programming language' do
before do
create_language(swift)
end
it_behaves_like 'performs detection', Projects::AppleTargetPlatformDetectorService
end
context 'when project uses Objective-C programming language' do
before do
create_language(objective_c)
end
include_examples 'performs detection'
it_behaves_like 'performs detection', Projects::AppleTargetPlatformDetectorService
end
end
context 'when project uses Objective-C programming language' do
let!(:repository_language) { create(:repository_language, project: project, programming_language: objective_c) }
context 'when project uses programming language for Android platform' do
let(:feature_enabled) { true }
let(:service_result) { %w(android) }
before do
stub_feature_flags(detect_android_projects: feature_enabled)
end
context 'when project uses Java' do
before do
create_language(java)
end
it_behaves_like 'performs detection', Projects::AndroidTargetPlatformDetectorService
context 'when feature flag is disabled' do
let(:feature_enabled) { false }
it_behaves_like 'does nothing'
end
end
context 'when project uses Kotlin' do
before do
create_language(kotlin)
end
it_behaves_like 'performs detection', Projects::AndroidTargetPlatformDetectorService
include_examples 'performs detection'
context 'when feature flag is disabled' do
let(:feature_enabled) { false }
it_behaves_like 'does nothing'
end
end
end
context 'when the project does not contain programming languages for Apple platforms' do
context 'when the project does not use programming languages for Apple or Android platforms' do
it_behaves_like 'does nothing'
end
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册