use super::Provider;
use crate::nixpacks::{
app::App,
environment::{Environment, EnvironmentVariables},
nix::pkg::Pkg,
phase::{BuildPhase, InstallPhase, SetupPhase, StartPhase},
};
use anyhow::Result;
pub struct GolangProvider {}
pub const BINARY_NAME: &'static &str = &"out";
const AVAILABLE_GO_VERSIONS: &[(&str, &str)] = &[("1.17", "go"), ("1.18", "go_1_18")];
pub const DEFAULT_GO_PKG_NAME: &'static &str = &"go";
impl Provider for GolangProvider {
fn name(&self) -> &str {
"golang"
}
fn detect(&self, app: &App, _env: &Environment) -> Result<bool> {
Ok(app.includes_file("main.go") || app.includes_file("go.mod"))
}
fn setup(&self, _app: &App, _env: &Environment) -> Result<Option<SetupPhase>> {
let go_mod = self.read_go_mod_if_exists(_app)?;
let nix_pkg = GolangProvider::get_nix_golang_pkg(go_mod)?;
Ok(Some(SetupPhase::new(vec![Pkg::new(&nix_pkg)])))
}
fn install(&self, app: &App, _env: &Environment) -> Result<Option<InstallPhase>> {
if app.includes_file("go.mod") {
return Ok(Some(InstallPhase::new("go get".to_string())));
}
Ok(None)
}
fn build(&self, app: &App, _env: &Environment) -> Result<Option<BuildPhase>> {
if app.includes_file("go.mod") {
Ok(Some(BuildPhase::new(format!(
"go build -o {}",
BINARY_NAME
))))
} else {
Ok(Some(BuildPhase::new(format!(
"go build -o {} main.go",
BINARY_NAME
))))
}
}
fn start(&self, _app: &App, env: &Environment) -> Result<Option<StartPhase>> {
let mut start_phase = StartPhase::new(format!("./{}", BINARY_NAME));
let cgo = env
.get_variable("CGO_ENABLED")
.cloned()
.unwrap_or_else(|| "0".to_string());
if cgo != "1" {
start_phase.run_in_slim_image();
}
Ok(Some(start_phase))
}
fn environment_variables(
&self,
_app: &App,
_env: &Environment,
) -> Result<Option<EnvironmentVariables>> {
Ok(Some(EnvironmentVariables::from([(
"CGO_ENABLED".to_string(),
"0".to_string(),
)])))
}
}
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> {
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.trim().split_whitespace().nth(1).unwrap();
if let Some(nix_pkg) = version_number_to_pkg(go_version)? {
return Ok(nix_pkg);
}
}
}
Ok(DEFAULT_GO_PKG_NAME.to_string())
}
}
fn version_number_to_pkg(version: &str) -> Result<Option<String>> {
let matched_version = AVAILABLE_GO_VERSIONS.iter().find(|(v, _)| v == &version);
match matched_version {
Some((_, pkg)) => Ok(Some(pkg.to_string())),
None => Ok(None),
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_no_go_mod() -> Result<()> {
assert_eq!(
GolangProvider::get_nix_golang_pkg(None)?,
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()))?,
"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()))?,
DEFAULT_GO_PKG_NAME.to_string()
);
Ok(())
}
}