//! The `go` template: a Go module built through dh-golang. //! //! The skeleton bodies (`go.mod`, `main.go`) are manifest data //! (`data/templates/go/manifest.yml`); the source stanza carries //! `XS-Go-Import-Path`, declared as the `{go_import_path}` placeholder by //! the manifest and filled here from the `module` line of `go.mod` when //! the packaged tree has one, defaulting to the package name (fresh //! skeletons embed the package name in their own `go.mod`). The module //! line is also the probe of an existing project. use std::path::Path; use super::{ProbeResult, TemplateHooks, source_dir_of}; use crate::new::options::NewOptions; /// The logic half of the go template. pub struct Hooks; /// The go template's hooks, registered in the template registry. pub static HOOKS: Hooks = Hooks; impl TemplateHooks for Hooks { /// The `{go_import_path}` value of the manifest's `XS-Go-Import-Path` /// source field (see [`import_path`]). fn context(&self, opts: &NewOptions) -> Vec<(String, String)> { vec![("go_import_path".to_string(), import_path(opts))] } /// Name (and default command) from the `module` line of `go.mod`: the /// last path segment is the conventional binary/package name. fn probe(&self, dir: &Path) -> Option { let module = read_module_line(dir)?; let name = module.rsplit('/').next()?.to_string(); if name.is_empty() { return None; } Some(ProbeResult { name: Some(name.clone()), command: Some(name), ..Default::default() }) } } /// The `XS-Go-Import-Path` value: probed from `go.mod` when the packaged /// tree has one, the package name otherwise (skeleton mode embeds the name /// in the generated `go.mod` anyway). fn import_path(opts: &NewOptions) -> String { source_dir_of(opts) .as_deref() .and_then(read_module_line) .unwrap_or_else(|| opts.name.clone()) } /// The `module ` line of `dir/go.mod`, when present. fn read_module_line(dir: &Path) -> Option { let content = std::fs::read_to_string(dir.join("go.mod")).ok()?; for line in content.lines() { let line = line.trim(); if let Some(rest) = line.strip_prefix("module ") { let module = rest.trim(); if !module.is_empty() { return Some(module.to_string()); } } } None } #[cfg(test)] mod tests { use super::*; use crate::new::options::{License, SourceDir, TemplateId}; use tempfile::tempdir; fn opts(source_dir: SourceDir) -> NewOptions { NewOptions { name: "mytool".into(), template: TemplateId::GO, source_dir, upstream_version: "0.1.0".into(), revision: 1, summary: "A tool".into(), long_description: "A tool".into(), homepage: None, license: License::Mit, command: "mytool".into(), maintainer: ("Jane".into(), "jane@example.com".into()), dist: "ubuntu".into(), series: "resolute".into(), release: false, depends: Vec::new(), source_format: crate::new::options::SourceFormat::Quilt, orig: Some(crate::new::options::OrigOrigin::Snapshot), git: true, autopkgtest: false, pkg_config: false, watch: None, } } #[test] fn go_template_shape() { let o = opts(SourceDir::Skeleton); let template = super::super::get(TemplateId::GO).unwrap(); assert_eq!(template.architecture(&o), "any"); assert_eq!( template.build_depends(&o), vec!["golang-any".to_string(), "dh-golang".to_string()] ); assert_eq!(template.rules_dh_line(), "dh $@ --buildsystem=golang"); assert!(template.debian(&o).is_empty()); // The skeleton bodies are manifest data now. let skeleton = template.skeleton(&o); assert!( skeleton .iter() .any(|f| f.path == "go.mod" && f.contents.starts_with("module mytool\n")) ); assert!(skeleton.iter().any(|f| f.path == "main.go")); // Skeleton mode: no go.mod to probe, the import path is the name. assert_eq!( template.source_fields(&o), vec![("XS-Go-Import-Path".to_string(), "mytool".to_string())] ); } #[test] fn go_probe_reads_module_line() { let dir = tempdir().unwrap(); std::fs::write( dir.path().join("go.mod"), "module example.com/org/mytool\n\ngo 1.21\n", ) .unwrap(); let template = super::super::get(TemplateId::GO).unwrap(); let probe = template.probe(dir.path()).expect("probe result"); assert_eq!(probe.name.as_deref(), Some("mytool")); assert_eq!(probe.command.as_deref(), Some("mytool")); // The probed module line wins over the package name for the import // path when packaging an existing tree. let o = opts(SourceDir::Path(dir.path().to_path_buf())); assert_eq!( template.source_fields(&o), vec![( "XS-Go-Import-Path".to_string(), "example.com/org/mytool".to_string() )] ); // No go.mod: no probe result. let empty = tempdir().unwrap(); assert!(template.probe(empty.path()).is_none()); } }