use super::Provider;
use crate::nixpacks::{
app::App,
environment::{Environment, EnvironmentVariables},
nix::pkg::Pkg,
plan::{
phase::{Phase, StartPhase},
BuildPlan,
},
};
use anyhow::Result;
pub struct GolangProvider {}
const BINARY_NAME: &str = "out";
const AVAILABLE_GO_VERSIONS: &[(&str, &str, &str)] = &[
(
"1.18",
"go_1_18",
"5148520bfab61f99fd25fb9ff7bfbb50dad3c9db",
),
(
"1.19",
"go_1_19",
"5148520bfab61f99fd25fb9ff7bfbb50dad3c9db",
),
(
"1.20",
"go_1_20",
"1f13eabcd6f5b00fe9de9575ac52c66a0e887ce6",
),
(
"1.21",
"go_1_21",
"1f13eabcd6f5b00fe9de9575ac52c66a0e887ce6",
),
("1.22", "go", "e89cf1c932006531f454de7d652163a9a5c86668"),
];
const DEFAULT_GO_PKG_NAME: &str = "go";
const DEFAULT_ARCHIVE: &str = "e89cf1c932006531f454de7d652163a9a5c86668";
const GO_BUILD_CACHE_DIR: &str = "/root/.cache/go-build";
impl Provider for GolangProvider {
fn name(&self) -> &str {
"go"
}
fn detect(&self, app: &App, _env: &Environment) -> Result<bool> {
Ok(app.includes_file("main.go") || app.includes_file("go.mod"))
}
fn get_build_plan(&self, app: &App, env: &Environment) -> Result<Option<BuildPlan>> {
let mut plan = BuildPlan::default();
let go_mod = self.read_go_mod_if_exists(app)?;
let (nix_pkg, archive) = GolangProvider::get_nix_golang_pkg(go_mod.as_ref())?;
let mut setup = Phase::setup(Some(vec![Pkg::new(&nix_pkg)]));
setup.set_nix_archive(archive);
plan.add_phase(setup);
if app.includes_file("go.mod") {
let mut install = Phase::install(Some("go mod download".to_string()));
install.add_cache_directory(GO_BUILD_CACHE_DIR.to_string());
plan.add_phase(install);
}
let mut build = if app.includes_file("go.mod") {
Phase::build(Some(format!("go build -o {BINARY_NAME}")))
} else if app.includes_file("main.go") {
Phase::build(Some(format!("go build -o {BINARY_NAME} main.go")))
} else {
Phase::build(None)
};
build.add_cache_directory(GO_BUILD_CACHE_DIR.to_string());
build.depends_on_phase("setup");
plan.add_phase(build);
let has_go_files = app.has_match("**/*.go");
if has_go_files {
let mut start = StartPhase::new(format!("./{BINARY_NAME}"));
let cgo = env.get_variable("CGO_ENABLED").unwrap_or("0");
if cgo != "1" {
start.run_in_slim_image();
}
plan.set_start_phase(start);
}
plan.add_variables(EnvironmentVariables::from([(
"CGO_ENABLED".to_string(),
"0".to_string(),
)]));
Ok(Some(plan))
}
}
impl GolangProvider {
pub fn read_go_mod_if_exists(&self, app: &App) -> Result<Option<String>> {
if app.includes_file("go.mod") {
Ok(Some(app.read_file("go.mod")?))
} else {
Ok(None)
}
}
pub fn get_nix_golang_pkg(go_mod_contents: Option<&String>) -> Result<(String, String)> {
if go_mod_contents.is_some() {
let mut lines = go_mod_contents.as_ref().unwrap().lines();
let go_version_line = lines.find(|line| line.trim().starts_with("go"));
if let Some(go_version_line) = go_version_line {
let go_version = go_version_line.split_whitespace().nth(1).unwrap();
let nix_pkg = version_number_to_pkg(go_version)
.unwrap_or_else(|| DEFAULT_GO_PKG_NAME.to_string());
let nix_archive = version_number_to_archive(go_version)
.unwrap_or_else(|| DEFAULT_ARCHIVE.to_string());
return Ok((nix_pkg, nix_archive));
}
}
Ok((DEFAULT_GO_PKG_NAME.to_string(), DEFAULT_ARCHIVE.to_string()))
}
}
fn version_number_to_pkg(version: &str) -> Option<String> {
let matched_version = AVAILABLE_GO_VERSIONS.iter().find(|(v, _, _)| v == &version);
matched_version.map(|(_, pkg, _)| (*pkg).to_string())
}
fn version_number_to_archive(version: &str) -> Option<String> {
let matched_version = AVAILABLE_GO_VERSIONS.iter().find(|(v, _, _)| v == &version);
matched_version.map(|(_, _, archive)| (*archive).to_string())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_no_go_mod() -> Result<()> {
assert_eq!(
GolangProvider::get_nix_golang_pkg(None)?.0,
DEFAULT_GO_PKG_NAME.to_string()
);
Ok(())
}
#[test]
fn test_with_go_mod() -> Result<()> {
let go_mod_contents = r"
go 1.18
";
assert_eq!(
GolangProvider::get_nix_golang_pkg(Some(&go_mod_contents.to_string()))?.0,
"go_1_18".to_string()
);
Ok(())
}
#[test]
fn test_fallback_on_invalid_version() -> Result<()> {
let go_mod_contents = r"
go 1.8
";
assert_eq!(
GolangProvider::get_nix_golang_pkg(Some(&go_mod_contents.to_string()))?.0,
DEFAULT_GO_PKG_NAME.to_string()
);
Ok(())
}
}