Skip to content

[WIP] Automatic Version Numbering #350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 11 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ Style/StringConcatenation:
# for more discussion
Style/FetchEnvVar:
Enabled: false

Style/OpenStructUse:
Enabled: false

########## Gemspec Rules

Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ _None_

### New Features

_None_
* Adds automatic version number calculation. [#350]

### Bug Fixes

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module Fastlane
module Actions
class AlphaBuildVersionAction < Action
def self.run(params)
require 'git'
require_relative '../../helper/version_helper'

helper = Fastlane::Helper::VersionHelper.new(git: Git.open(params[:project_root]))

{
build_name: helper.alpha_build_name,
build_number: helper.alpha_build_number
}
end

#####################################################
# @!group Documentation
#####################################################

def self.description
'Return a prototype build version based on CI environment variables, and the current state of the repo'
end

def self.available_options
[
FastlaneCore::ConfigItem.new(
key: :project_root,
env_name: 'PROJECT_ROOT_FOLDER',
description: 'The project root folder (that contains the .git directory)',
type: String,
default_value: Dir.pwd,
verify_block: proc { |v| UI.user_error!("Directory does not exist: #{v}") unless File.directory? v }
),
]
end

def self.authors
['Automattic']
end

def self.is_supported?(platform)
true
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
module Fastlane
module Actions
class NextRcVersionAction < Action
def self.run(params)
require 'octokit'
require 'git'
require_relative '../../helper/version_helper'

client = Octokit::Client.new(access_token: params[:access_token])
client.auto_paginate = true

helper = Fastlane::Helper::VersionHelper.new(git: Git.open(params[:project_root]))

version = Fastlane::Helper::Version.create(params[:version])
next_version = helper.next_rc_for_version(version, repository: params[:project], github_client: client)

UI.message "Next RC Version is #{next_version.rc}"

next_version
end

#####################################################
# @!group Documentation
#####################################################

def self.description
'Return the next RC Version for this branch'
end

def self.available_options
[
FastlaneCore::ConfigItem.new(
key: :access_token,
env_name: 'GITHUB_TOKEN',
description: 'The GitHub token to use when querying GitHub',
type: String,
sensitive: true
),
FastlaneCore::ConfigItem.new(
key: :version,
description: 'The current version',
type: String,
verify_block: proc { |v| UI.user_error!("Invalid version number: #{v}") if Fastlane::Helper::Version.create(v).nil? }
),
FastlaneCore::ConfigItem.new(
key: :project,
description: 'The project slug (ex: `wordpress-mobile/wordpress-ios`)',
type: String
),
FastlaneCore::ConfigItem.new(
key: :project_root,
env_name: 'PROJECT_ROOT_FOLDER',
description: 'The project root folder (that contains the .git directory)',
type: String,
default_value: Dir.pwd,
verify_block: proc { |v| UI.user_error!("Directory does not exist: #{v}") unless File.directory? v }
),
]
end

def self.authors
['Automattic']
end

def self.is_supported?(platform)
true
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module Fastlane
module Actions
class PrototypeBuildVersionAction < Action
def self.run(params)
require 'git'
require_relative '../../helper/version_helper'

helper = Fastlane::Helper::VersionHelper.new(git: Git.open(params[:project_root]))

{
build_name: helper.prototype_build_name,
build_number: helper.prototype_build_number
}
end

#####################################################
# @!group Documentation
#####################################################

def self.description
'Return a prototype build version based on CI environment variables, and the current state of the repo'
end

def self.available_options
[
FastlaneCore::ConfigItem.new(
key: :project_root,
env_name: 'PROJECT_ROOT_FOLDER',
description: 'The project root folder (that contains the .git directory)',
type: String,
default_value: Dir.pwd,
verify_block: proc { |v| UI.user_error!("Directory does not exist: #{v}") unless File.directory? v }
),
]
end

def self.authors
['Automattic']
end

def self.is_supported?(platform)
true
end
end
end
end
101 changes: 101 additions & 0 deletions lib/fastlane/plugin/wpmreleasetoolkit/helper/version_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
module Fastlane
module Helper
class VersionHelper
def initialize(git: nil)
@git = git || Git.open(Dir.pwd)
end

# Generate a prototype build name based on the current pr_number and commit.
#
# Takes optional `pr_number:` and `commit:` arguments to generate a build name
# based on a different pr_number and commit.
def prototype_build_name(pr_number: nil, commit: nil)
pr_number ||= current_pr_number
commit ||= current_commit

UI.user_error!('Unable to find a PR in the environment – falling back to a branch-based version name. To run this in a development environment, try: `export LOCAL_PR_NUMBER=1234`') if pr_number.nil?

"pr-#{pr_number}-#{commit.sha[0, 7]}"
end

# Generate a prototype build number based on the most recent commit.
#
# Takes an optional `commit:` argument to generate a build number based
# on a different commit.
def prototype_build_number(commit: nil)
commit ||= current_commit
commit.date.to_i
end

# Generate an alpha build name based on the current branch and commit.
#
# Takes optional `branch:` and `commit:` arguments to generate a build name
# based on a different branch and commit.
def alpha_build_name(branch: nil, commit: nil)
branch ||= current_branch
commit ||= current_commit

"#{branch}-#{commit.sha[0, 7]}"
end

# Generate an alpha number.
#
# Allows injecting a specific `DateTime` to derive the build number from
def alpha_build_number(now: DateTime.now)
now.to_i
end

# Find the newest rc of a specific version in a given GitHub repository.
def newest_rc_for_version(version, repository:, github_client:)
tags = github_client.tags(repository)

# GitHub Enterprise can return raw HTML if the connection isn't
# working, so we need to validate that this is what we expect it is
UI.crash! 'Unable to connect to GitHub. Please try again later.' unless tags.is_a? Array

tags.map { |t| Version.create(t[:name]) }
.compact
.filter { |v| v.is_rc_of(version) }
.sort
.reverse
.first
end

# Given the current version of an app and its Git Repository,
# use the existing tags to figure out which RC version should be
# the next one.
def next_rc_for_version(version, repository:, github_client:)
most_recent_rc_version = newest_rc_for_version(version, repository: repository, github_client: github_client)

# If there is no RC tag, this must be the first one ever
return version.next_rc_version if most_recent_rc_version.nil?

# If we have a previous RC for this version, we can just bump it
most_recent_rc_version.next_rc_version
end

private

# Get the most recent commit on the current branch of the Git repository
def current_commit
@git.log.first
end

# Get the current branch of the Git repository
def current_branch
@git.current_branch
end

# Get the current PR number from the CI environment
def current_pr_number
%w[
BUILDKITE_PULL_REQUEST
CIRCLE_PR_NUMBER
LOCAL_PR_NUMBER
].map { |k| ENV[k] }
.compact
.first
end
end
end
end
Loading