///|
/// Detect CI information from environment variables only.
///
/// This pure entry point does not run git commands or read provider event
/// files. Use `env_ci_from` when you also want the local git fallback.
pub fn detect_from_env(env : Map[String, String]) -> Info? {
if env_has(env, "APPVEYOR") {
Some(appveyor_info(env))
} else if env_has(env, "bamboo_agentId") {
Some(bamboo_info(env))
} else if env_has(env, "BITBUCKET_BUILD_NUMBER") {
Some(bitbucket_info(env))
} else if env_has(env, "BITRISE_IO") {
Some(bitrise_info(env))
} else if env_has(env, "BUDDY_WORKSPACE_ID") {
Some(buddy_info(env))
} else if env_has(env, "BUILDKITE") {
Some(buildkite_info(env))
} else if env_has(env, "CIRCLECI") {
Some(circleci_info(env))
} else if env_has(env, "CIRRUS_CI") {
Some(cirrus_info(env))
} else if env_has(env, "CODEBUILD_BUILD_ID") {
Some(codebuild_info(env))
} else if env_has(env, "CF_BUILD_ID") {
Some(codefresh_info(env))
} else if env_get(env, "CI_NAME") == Some("codeship") {
Some(codeship_info(env))
} else if env_has(env, "DRONE") {
Some(drone_info(env))
} else if env_has(env, "GITHUB_ACTION") {
Some(github_info(env, pr_event=None))
} else if env_has(env, "GITLAB_CI") {
Some(gitlab_info(env))
} else if env_has(env, "JENKINS_URL") {
Some(jenkins_info(env, git_head=None))
} else if env_has(env, "SAILCI") {
Some(sail_info(env))
} else if env_has(env, "SEMAPHORE") {
Some(semaphore_info(env, git_head=None))
} else if env_has(env, "SHIPPABLE") {
Some(shippable_info(env))
} else if env_has(env, "TRAVIS") {
Some(travis_info(env))
} else if env_has(env, "BUILD_BUILDURI") {
Some(vsts_info(env))
} else if env_has(env, "WERCKER_MAIN_PIPELINE_STARTED") {
Some(wercker_info(env))
} else {
None
}
}
///|
fn appveyor_info(env : Map[String, String]) -> Info {
let pr = env_get(env, "APPVEYOR_PULL_REQUEST_NUMBER")
let is_pr = pr is Some(_)
{
..base_info("Appveyor", "appveyor", is_ci=true),
commit: env_get(env, "APPVEYOR_REPO_COMMIT"),
tag: env_get(env, "APPVEYOR_REPO_TAG_NAME"),
build: env_get(env, "APPVEYOR_BUILD_NUMBER"),
build_url: map2(
env_get(env, "APPVEYOR_PROJECT_SLUG"),
env_get(env, "APPVEYOR_BUILD_VERSION"),
(slug, version) => {
"https://ci.appveyor.com/project/\{slug}/build/\{version}"
},
),
branch: env_get(env, "APPVEYOR_REPO_BRANCH"),
job: env_get(env, "APPVEYOR_JOB_NUMBER"),
job_url: map2(
env_get(env, "APPVEYOR_PROJECT_SLUG"),
env_get(env, "APPVEYOR_JOB_ID"),
(slug, job) => "https://ci.appveyor.com/project/\{slug}/build/job/\{job}",
),
pr,
is_pr: Some(is_pr),
pr_branch: env_get(env, "APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH"),
slug: env_get(env, "APPVEYOR_REPO_NAME"),
root: env_get(env, "APPVEYOR_BUILD_FOLDER"),
}
}
///|
fn bamboo_info(env : Map[String, String]) -> Info {
{
..base_info("Bamboo", "bamboo", is_ci=true),
commit: env_get(env, "bamboo_planRepository_1_revision"),
build: env_get(env, "bamboo_buildNumber"),
build_url: env_get(env, "bamboo_buildResultsUrl"),
branch: env_get(env, "bamboo_planRepository_1_branchName"),
job: env_get(env, "bamboo_buildKey"),
root: env_get(env, "bamboo_build_working_directory"),
}
}
///|
fn bitbucket_info(env : Map[String, String]) -> Info {
{
..base_info("Bitbucket Pipelines", "bitbucket", is_ci=true),
commit: env_get(env, "BITBUCKET_COMMIT"),
tag: env_get(env, "BITBUCKET_TAG"),
build: env_get(env, "BITBUCKET_BUILD_NUMBER"),
build_url: map2(
env_get(env, "BITBUCKET_REPO_SLUG"),
env_get(env, "BITBUCKET_BUILD_NUMBER"),
(slug, build) => {
"https://bitbucket.org/\{slug}/addon/pipelines/home#!/results/\{build}"
},
),
branch: env_get(env, "BITBUCKET_BRANCH"),
slug: env_get(env, "BITBUCKET_REPO_SLUG"),
root: env_get(env, "BITBUCKET_CLONE_DIR"),
}
}
///|
fn bitrise_info(env : Map[String, String]) -> Info {
let pr = false_string_as_none(env_get(env, "BITRISE_PULL_REQUEST"))
let is_pr = pr is Some(_)
{
..base_info("Bitrise", "bitrise", is_ci=true),
commit: env_get(env, "BITRISE_GIT_COMMIT"),
tag: env_get(env, "BITRISE_GIT_TAG"),
build: env_get(env, "BITRISE_BUILD_NUMBER"),
build_url: env_get(env, "BITRISE_BUILD_URL"),
branch: if is_pr {
env_get(env, "BITRISEIO_GIT_BRANCH_DEST")
} else {
env_get(env, "BITRISE_GIT_BRANCH")
},
pr,
is_pr: Some(is_pr),
pr_branch: if is_pr {
env_get(env, "BITRISE_GIT_BRANCH")
} else {
None
},
slug: env_get(env, "BITRISE_APP_SLUG"),
}
}
///|
fn buddy_info(env : Map[String, String]) -> Info {
let pr = last_number(env_get(env, "BUDDY_EXECUTION_PULL_REQUEST_ID"))
let is_pr = pr is Some(_)
{
..base_info("Buddy", "buddy", is_ci=true),
commit: env_get(env, "BUDDY_EXECUTION_REVISION"),
tag: env_get(env, "BUDDY_EXECUTION_TAG"),
build: env_get(env, "BUDDY_EXECUTION_ID"),
build_url: env_get(env, "BUDDY_EXECUTION_URL"),
branch: if is_pr {
None
} else {
env_get(env, "BUDDY_EXECUTION_BRANCH")
},
pr,
is_pr: Some(is_pr),
slug: env_get(env, "BUDDY_REPO_SLUG"),
}
}
///|
fn buildkite_info(env : Map[String, String]) -> Info {
let pr = false_string_as_none(env_get(env, "BUILDKITE_PULL_REQUEST"))
let is_pr = pr is Some(_)
{
..base_info("Buildkite", "buildkite", is_ci=true),
build: env_get(env, "BUILDKITE_BUILD_NUMBER"),
build_url: env_get(env, "BUILDKITE_BUILD_URL"),
commit: env_get(env, "BUILDKITE_COMMIT"),
tag: env_get(env, "BUILDKITE_TAG"),
branch: if is_pr {
env_get(env, "BUILDKITE_PULL_REQUEST_BASE_BRANCH")
} else {
env_get(env, "BUILDKITE_BRANCH")
},
slug: join2(
env_get(env, "BUILDKITE_ORGANIZATION_SLUG"),
"/",
env_get(env, "BUILDKITE_PROJECT_SLUG"),
),
pr,
is_pr: Some(is_pr),
pr_branch: if is_pr {
env_get(env, "BUILDKITE_BRANCH")
} else {
None
},
root: env_get(env, "BUILDKITE_BUILD_CHECKOUT_PATH"),
}
}
///|
fn circleci_info(env : Map[String, String]) -> Info {
let pr = match env_get(env, "CIRCLE_PR_NUMBER") {
Some(value) => Some(value)
None =>
last_number(env_first(env, ["CIRCLE_PULL_REQUEST", "CI_PULL_REQUEST"]))
}
let is_pr = pr is Some(_)
{
..base_info("CircleCI", "circleci", is_ci=true),
build: env_get(env, "CIRCLE_BUILD_NUM"),
build_url: env_get(env, "CIRCLE_BUILD_URL"),
job: map2(
env_get(env, "CIRCLE_BUILD_NUM"),
env_get(env, "CIRCLE_NODE_INDEX"),
(build, index) => "\{build}.\{index}",
),
commit: env_get(env, "CIRCLE_SHA1"),
tag: env_get(env, "CIRCLE_TAG"),
branch: if is_pr {
None
} else {
env_get(env, "CIRCLE_BRANCH")
},
pr,
is_pr: Some(is_pr),
pr_branch: if is_pr {
env_get(env, "CIRCLE_BRANCH")
} else {
None
},
slug: join2(
env_get(env, "CIRCLE_PROJECT_USERNAME"),
"/",
env_get(env, "CIRCLE_PROJECT_REPONAME"),
),
}
}
///|
fn cirrus_info(env : Map[String, String]) -> Info {
let pr = env_get(env, "CIRRUS_PR")
let is_pr = pr is Some(_)
{
..base_info("Cirrus CI", "cirrus", is_ci=true),
commit: env_get(env, "CIRRUS_CHANGE_IN_REPO"),
tag: env_get(env, "CIRRUS_TAG"),
build: env_get(env, "CIRRUS_BUILD_ID"),
build_url: env_get(env, "CIRRUS_BUILD_ID").map(build => {
"https://cirrus-ci.com/build/\{build}"
}),
job: env_get(env, "CIRRUS_TASK_ID"),
job_url: env_get(env, "CIRRUS_TASK_ID").map(task => {
"https://cirrus-ci.com/task/\{task}"
}),
branch: if is_pr {
env_get(env, "CIRRUS_BASE_BRANCH")
} else {
env_get(env, "CIRRUS_BRANCH")
},
pr,
is_pr: Some(is_pr),
slug: env_get(env, "CIRRUS_REPO_FULL_NAME"),
root: env_get(env, "CIRRUS_WORKING_DIR"),
}
}
///|
fn codebuild_info(env : Map[String, String]) -> Info {
{
..base_info("AWS CodeBuild", "codebuild", is_ci=true),
build: env_get(env, "CODEBUILD_BUILD_ID"),
build_url: map2(
env_get(env, "AWS_REGION"),
env_get(env, "CODEBUILD_BUILD_ID"),
(region, build) => {
"https://console.aws.amazon.com/codebuild/home?region=\{region}#/builds/\{build}/view/new"
},
),
root: env_get(env, "PWD"),
}
}
///|
fn codefresh_info(env : Map[String, String]) -> Info {
let pr = env_get(env, "CF_PULL_REQUEST_NUMBER")
let is_pr = pr is Some(_)
{
..base_info("Codefresh", "codefresh", is_ci=true),
commit: env_get(env, "CF_REVISION"),
build: env_get(env, "CF_BUILD_ID"),
build_url: env_get(env, "CF_BUILD_URL"),
branch: if is_pr {
env_get(env, "CF_PULL_REQUEST_TARGET")
} else {
env_get(env, "CF_BRANCH")
},
pr,
is_pr: Some(is_pr),
pr_branch: if is_pr {
env_get(env, "CF_BRANCH")
} else {
None
},
slug: join2(
env_get(env, "CF_REPO_OWNER"),
"/",
env_get(env, "CF_REPO_NAME"),
),
root: env_get(env, "CF_VOLUME_PATH"),
}
}
///|
fn codeship_info(env : Map[String, String]) -> Info {
{
..base_info("Codeship", "codeship", is_ci=true),
build: env_get(env, "CI_BUILD_NUMBER"),
build_url: env_get(env, "CI_BUILD_URL"),
commit: env_get(env, "CI_COMMIT_ID"),
branch: env_get(env, "CI_BRANCH"),
slug: env_get(env, "CI_REPO_NAME"),
}
}
///|
fn drone_info(env : Map[String, String]) -> Info {
let is_pr = env_get(env, "DRONE_BUILD_EVENT") == Some("pull_request")
{
..base_info("Drone", "drone", is_ci=true),
commit: env_get(env, "DRONE_COMMIT_SHA"),
tag: env_get(env, "DRONE_TAG"),
build: env_get(env, "DRONE_BUILD_NUMBER"),
build_url: env_get(env, "DRONE_BUILD_LINK"),
branch: if is_pr {
env_get(env, "DRONE_TARGET_BRANCH")
} else {
env_get(env, "DRONE_BRANCH")
},
job: env_get(env, "DRONE_JOB_NUMBER"),
job_url: env_get(env, "DRONE_BUILD_LINK"),
pr: env_get(env, "DRONE_PULL_REQUEST"),
is_pr: Some(is_pr),
pr_branch: if is_pr {
env_get(env, "DRONE_SOURCE_BRANCH")
} else {
None
},
slug: join2(
env_get(env, "DRONE_REPO_OWNER"),
"/",
env_get(env, "DRONE_REPO_NAME"),
),
root: env_get(env, "DRONE_WORKSPACE"),
}
}
///|
fn github_info(env : Map[String, String], pr_event~ : GithubPrEvent?) -> Info {
let is_pr = env_get(env, "GITHUB_EVENT_NAME") == Some("pull_request")
let ref_branch = branch_from_ref(env_get(env, "GITHUB_REF"))
let event_branch = match pr_event {
Some(event) => event.branch
None => None
}
let event_pr = match pr_event {
Some(event) => event.pr
None => None
}
{
..base_info("GitHub Actions", "github", is_ci=true),
commit: env_get(env, "GITHUB_SHA"),
is_pr: Some(is_pr),
branch: if is_pr {
optional_or(event_branch, ref_branch)
} else {
ref_branch
},
pr_branch: if is_pr {
ref_branch
} else {
None
},
pr: if is_pr {
event_pr
} else {
None
},
slug: env_get(env, "GITHUB_REPOSITORY"),
root: env_get(env, "GITHUB_WORKSPACE"),
}
}
///|
fn gitlab_info(env : Map[String, String]) -> Info {
{
..base_info("GitLab CI/CD", "gitlab", is_ci=true),
commit: env_get(env, "CI_COMMIT_SHA"),
tag: env_get(env, "CI_COMMIT_TAG"),
build: env_get(env, "CI_PIPELINE_ID"),
build_url: map2(
env_get(env, "CI_PROJECT_URL"),
env_get(env, "CI_PIPELINE_ID"),
(url, pipeline) => "\{url}/pipelines/\{pipeline}",
),
job: env_get(env, "CI_JOB_ID"),
job_url: map2(env_get(env, "CI_PROJECT_URL"), env_get(env, "CI_JOB_ID"), (
url,
job,
) => "\{url}/-/jobs/\{job}"),
branch: env_get(env, "CI_COMMIT_REF_NAME"),
slug: env_get(env, "CI_PROJECT_PATH"),
root: env_get(env, "CI_PROJECT_DIR"),
}
}
///|
fn jenkins_info(env : Map[String, String], git_head~ : String?) -> Info {
let pr = env_first(env, ["ghprbPullId", "gitlabMergeRequestId", "CHANGE_ID"])
let is_pr = pr is Some(_)
let local_branch = env_first(env, [
"GIT_LOCAL_BRANCH", "GIT_BRANCH", "gitlabBranch", "BRANCH_NAME",
])
{
..base_info("Jenkins", "jenkins", is_ci=true),
commit: optional_or(
env_first(env, ["ghprbActualCommit", "GIT_COMMIT"]),
git_head,
),
branch: if is_pr {
env_first(env, ["ghprbTargetBranch", "gitlabTargetBranch"])
} else {
local_branch
},
build: env_get(env, "BUILD_NUMBER"),
build_url: env_get(env, "BUILD_URL"),
root: env_get(env, "WORKSPACE"),
pr,
is_pr: Some(is_pr),
pr_branch: if is_pr {
optional_or(
env_first(env, ["ghprbSourceBranch", "gitlabSourceBranch"]),
local_branch,
)
} else {
None
},
}
}
///|
fn sail_info(env : Map[String, String]) -> Info {
let pr = env_get(env, "SAIL_PULL_REQUEST_NUMBER")
let is_pr = pr is Some(_)
{
..base_info("Sail CI", "sail", is_ci=true),
commit: env_get(env, "SAIL_COMMIT_SHA"),
branch: if is_pr {
None
} else {
env_get(env, "SAIL_COMMIT_BRANCH")
},
pr,
is_pr: Some(is_pr),
slug: join2(
env_get(env, "SAIL_REPO_OWNER"),
"/",
env_get(env, "SAIL_REPO_NAME"),
),
root: env_get(env, "SAIL_CLONE_DIR"),
}
}
///|
fn semaphore_info(env : Map[String, String], git_head~ : String?) -> Info {
let pr = env_get(env, "PULL_REQUEST_NUMBER")
let is_pr = pr is Some(_)
{
..base_info("Semaphore", "semaphore", is_ci=true),
commit: git_head,
build: env_get(env, "SEMAPHORE_BUILD_NUMBER"),
branch: if is_pr {
None
} else {
env_get(env, "BRANCH_NAME")
},
pr,
is_pr: Some(is_pr),
pr_branch: if is_pr {
env_get(env, "BRANCH_NAME")
} else {
None
},
slug: env_get(env, "SEMAPHORE_REPO_SLUG"),
root: env_get(env, "SEMAPHORE_PROJECT_DIR"),
}
}
///|
fn shippable_info(env : Map[String, String]) -> Info {
let pr = if env_get(env, "IS_PULL_REQUEST") == Some("true") {
env_get(env, "PULL_REQUEST")
} else {
None
}
let is_pr = pr is Some(_)
{
..base_info("Shippable", "shippable", is_ci=true),
commit: env_get(env, "COMMIT"),
tag: env_get(env, "GIT_TAG_NAME"),
build: env_get(env, "BUILD_NUMBER"),
build_url: env_get(env, "BUILD_URL"),
branch: if is_pr {
env_get(env, "BASE_BRANCH")
} else {
env_get(env, "BRANCH")
},
job: env_get(env, "JOB_NUMBER"),
pr,
is_pr: Some(is_pr),
pr_branch: if is_pr {
env_get(env, "HEAD_BRANCH")
} else {
None
},
slug: env_get(env, "SHIPPABLE_REPO_SLUG"),
root: env_get(env, "SHIPPABLE_BUILD_DIR"),
}
}
///|
fn travis_info(env : Map[String, String]) -> Info {
let pr = false_string_as_none(env_get(env, "TRAVIS_PULL_REQUEST"))
let is_pr = pr is Some(_)
{
..base_info("Travis CI", "travis", is_ci=true),
commit: env_get(env, "TRAVIS_COMMIT"),
tag: env_get(env, "TRAVIS_TAG"),
build: env_get(env, "TRAVIS_BUILD_NUMBER"),
build_url: env_get(env, "TRAVIS_BUILD_WEB_URL"),
branch: env_get(env, "TRAVIS_BRANCH"),
job: env_get(env, "TRAVIS_JOB_NUMBER"),
job_url: env_get(env, "TRAVIS_JOB_WEB_URL"),
pr,
is_pr: Some(is_pr),
pr_branch: env_get(env, "TRAVIS_PULL_REQUEST_BRANCH"),
slug: env_get(env, "TRAVIS_REPO_SLUG"),
root: env_get(env, "TRAVIS_BUILD_DIR"),
}
}
///|
fn vsts_info(env : Map[String, String]) -> Info {
let pr = env_get(env, "SYSTEM_PULLREQUEST_PULLREQUESTID")
let is_pr = pr is Some(_)
{
..base_info("Visual Studio Team Services", "vsts", is_ci=true),
commit: env_get(env, "BUILD_SOURCEVERSION"),
build: env_get(env, "BUILD_BUILDNUMBER"),
branch: if is_pr {
env_get(env, "SYSTEM_PULLREQUEST_TARGETBRANCH")
} else {
env_get(env, "BUILD_SOURCEBRANCHNAME")
},
pr,
is_pr: Some(is_pr),
pr_branch: if is_pr {
env_get(env, "SYSTEM_PULLREQUEST_SOURCEBRANCH")
} else {
None
},
root: env_get(env, "BUILD_REPOSITORY_LOCALPATH"),
}
}
///|
fn wercker_info(env : Map[String, String]) -> Info {
{
..base_info("Wercker", "wercker", is_ci=true),
commit: env_get(env, "WERCKER_GIT_COMMIT"),
build: env_get(env, "WERCKER_MAIN_PIPELINE_STARTED"),
build_url: env_get(env, "WERCKER_RUN_URL"),
branch: env_get(env, "WERCKER_GIT_BRANCH"),
slug: join2(
env_get(env, "WERCKER_GIT_OWNER"),
"/",
env_get(env, "WERCKER_GIT_REPOSITORY"),
),
root: env_get(env, "WERCKER_ROOT"),
}
}