package pack import ( "archive/zip" "io" "os" "path/filepath" ) // ZipDist creates a ZIP file with filenam zipname. Extracting the package will // create a folder with name dist. The contents of the distribution is taken // from distDir. func ZipDist(zipname string, dist, distDir string) error { zf, err := os.Create(zipname) if err != nil { return err } defer zf.Close() wd, err := os.Getwd() if err != nil { return err } if err = os.Chdir(distDir); err != nil { return err } defer os.Chdir(wd) zipw := zip.NewWriter(zf) defer zipw.Close() err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error { if info.IsDir() { return nil } stat, err := os.Stat(path) if err != nil { return err } zhdr, err := zip.FileInfoHeader(stat) if err != nil { return err } zhdr.Name = filepath.Join(dist, path) zhdr.Method = zip.Deflate zwr, err := zipw.CreateHeader(zhdr) if err != nil { return err } rd, err := os.Open(path) if err != nil { return err } defer rd.Close() io.Copy(zwr, rd) return nil }) return err }
package pack import ( "fmt" "io" "os" "path/filepath" "runtime" ) type OsDepNames = map[string]string var OsDepExe = OsDepNames{ "windows": "%s.exe", } // CopyFile copies file with path src to file with path dst. It also tarnsfers // the file mode from the src file to the dst file. func CopyFile(dst, src string, osdn OsDepNames) error { if osdn != nil { if pat, ok := osdn[runtime.GOOS]; ok { src = fmt.Sprintf(pat, src) dst = fmt.Sprintf(pat, dst) } } df, err := os.Create(dst) if err != nil { return err } defer df.Close() sf, err := os.Open(src) if err != nil { return err } defer sf.Close() log.Infoa("copy `src` → `dst`", src, dst) _, err = io.Copy(df, sf) if err != nil { return err } stat, err := os.Stat(src) if err != nil { return err } err = os.Chmod(dst, stat.Mode()) return err } // CopyToDir copies a list of files to a single destination directory using // CopyFile on each source file. func CopyToDir(dst string, osdn OsDepNames, files ...string) error { for _, f := range files { b := filepath.Base(f) dst := filepath.Join(dst, b) err := CopyFile(dst, f, osdn) if err != nil { return err } } return nil } func walk(root string, do func(dir string, info os.FileInfo) (bool, error)) error { if stat, err := os.Stat(root); err != nil { return err } else if enter, err := do(filepath.Dir(root), stat); err != nil { return err } else if enter { log.Infoa("walk into `dir`", root) rddir, err := os.Open(root) if err != nil { return err } defer rddir.Close() for infos, err := rddir.Readdir(1); err != io.EOF; infos, err = rddir.Readdir(1) { if err != nil { return err } info := infos[0] if info.IsDir() { err = walk(filepath.Join(root, info.Name()), do) if err != nil { return err } } else { done, err := do(root, info) if err != nil { return err } if done { log.Infoa("done with `dir` after `file`", root, info.Name()) return nil } } } log.Infoa("walk out of `dir`", root) } else { log.Infoa("walk around `dir`", root) } return nil } // Search file tree root for files that match filer and copy them to the single // destination directory dst. If filter is nil all files are collected. func CollectToDir( dst, root string, filter func(dir string, file os.FileInfo) bool, osdn OsDepNames, ) error { return walk(root, func(dir string, info os.FileInfo) (bool, error) { pass := true if filter != nil { pass = filter(dir, info) } switch { case info.IsDir(): return pass, nil case pass: err := CopyToDir(dst, osdn, filepath.Join(dir, info.Name())) return false, err default: return false, nil } }) } // CopyRecursive copies the content of the src directory into the dst directory. // If filter is not nil only those files or subdirectories are copied for which // the filter returns true. func CopyRecursive( dst, src string, filter func(dir string, info os.FileInfo) bool, osdn OsDepNames, ) error { dst = filepath.Clean(dst) src = filepath.Clean(src) return walk(src, func(dir string, info os.FileInfo) (bool, error) { pass := true if filter != nil { pass = filter(dir, info) } switch { case info.IsDir(): return pass, nil case pass: rel, err := filepath.Rel(src, dir) if err != nil { return false, err } toDir := filepath.Join(dst, rel) err = os.MkdirAll(toDir, 0777) if err != nil { return false, err } err = CopyToDir(toDir, osdn, filepath.Join(dir, info.Name())) return false, err default: return false, nil } }) } // CopyTree copies the directory tree src into the dst directory, i.e. on succes // the directory dst will contain one, perhaps new, directory src. func CopyTree( dst, src string, osdn OsDepNames, filter func(dir string, info os.FileInfo) bool, ) error { dst = filepath.Join(dst, filepath.Base(src)) err := os.Mkdir(dst, 0777) if err != nil { return err } err = CopyRecursive(dst, src, filter, osdn) return err }
package pack import ( "fmt" "git.fractalqb.de/fractalqb/c4hgol" "git.fractalqb.de/fractalqb/qbsllm" ) var ( log = qbsllm.New(qbsllm.Lnormal, "pack", nil, nil) LogCfg = c4hgol.Config(qbsllm.NewConfig(log)) ) func Task(name string, do func() error) (err error) { log.Infoa("begin `task`", name) defer func() { if x := recover(); x != nil { switch e := x.(type) { case error: err = e default: err = fmt.Errorf("error: %v", x) } } if err != nil { log.Errora("`task` failed with `error`", name, err) } else { log.Infoa("done with `task`", name) } }() err = do() return err }
package main import ( "bufio" "flag" "fmt" "go/ast" "go/format" "go/token" "io" "os" "path/filepath" "regexp" "runtime" "strconv" "strings" "time" "git.fractalqb.de/fractalqb/nmconv" "git.fractalqb.de/fractalqb/pack" "git.fractalqb.de/fractalqb/qbsllm" ) var log = qbsllm.New(qbsllm.Lnormal, "versioner", nil, nil) var mkName = nmconv.Conversion{ Denorm: nmconv.Camel1Up, Norm: nmconv.Unsep("_"), } var ( semVerNoPattern = regexp.MustCompile(`^([0-9]|([1-9][0-9]*))$`) semVerIdent = regexp.MustCompile(`^[0-9a-zA-Z-]+$`) ) func checkSemVerNo(k, v string) { if !semVerNoPattern.MatchString(v) { log.Fatala("`num` is not a valid SemVer number for `part`", v, k) } } func checkSemVerId(k, v string) { if !semVerIdent.MatchString(v) { log.Fatala("`id` is not a valid SemVer identifyer for `tag`", v, k) } } type att struct { key, val string } func readInFile(filename string) []att { f, err := os.Open(filename) if err != nil { log.Fatale(err) } defer f.Close() return readInput(f) } func readInput(rd io.Reader) (res []att) { scn := bufio.NewScanner(rd) var semMajor, semMinor, semPatch bool for scn.Scan() { line := scn.Text() if len(strings.TrimSpace(line)) == 0 { continue } sep := strings.IndexRune(line, '=') if sep > 0 { k, v := line[:sep], line[sep+1:] switch strings.ToLower(k) { case "major": semMajor = true if !flagNoSemVer { checkSemVerNo(k, v) } case "minor": semMinor = true if !flagNoSemVer { checkSemVerNo(k, v) } case "patch": semPatch = true if !flagNoSemVer { checkSemVerNo(k, v) } default: if !flagNoSemVer { checkSemVerId(k, v) } } a := att{key: k, val: v} res = append(res, a) } else { log.Fatala("syntax error in `line`", line) } } if !flagNoSemVer { if !semMajor { log.Fatals("missing major version, set value 'major'") } if !semMinor { log.Fatals("missing minor version, set value 'minor'") } if !semPatch { log.Fatals("missing patch version, set value 'patch'") } } return res } func writeInFile(filename string, attls []att) { tmpnm := filename + "~" f, err := os.Create(tmpnm) if err != nil { log.Fatale(err) } defer func() { if f != nil { f.Close() } }() writeInput(f, attls) err = f.Close() f = nil if err != nil { log.Fatale(err) } os.Rename(tmpnm, filename) } func writeInput(wr io.Writer, attls []att) { for _, a := range attls { fmt.Fprintf(wr, "%s=%s\n", a.key, a.val) } } func needQuote(s string) bool { if s == "true" || s == "false" { return false } else if _, err := strconv.ParseInt(s, 10, 64); err == nil { return false } else if _, err := strconv.ParseUint(s, 10, 64); err == nil { return false } else if _, err := strconv.ParseFloat(s, 64); err == nil { return false } return true } func quote(s string) string { if needQuote(s) { return "`" + s + "`" } return s } func mkVSpec(name, value string, kind token.Token) *ast.ValueSpec { res := &ast.ValueSpec{ Names: []*ast.Ident{&ast.Ident{Name: flagPfx + name}}, Values: []ast.Expr{ &ast.BasicLit{ Kind: kind, Value: quote(value), }, }, } return res } func makeAst(atts []att) (*ast.File, []att) { var vals []ast.Spec hadBNo := false for i, a := range atts { atnm := mkName.Convert(a.key) atkind := token.STRING atval := a.val if a.key == flagBno { n, err := strconv.Atoi(a.val) if err != nil { log.Fatala("invalid build `number` in `attribute`", a.val, a.key) } atval = strconv.Itoa(n + 1) atkind = token.INT hadBNo = true atts[i].val = atval } spec := mkVSpec(atnm, atval, atkind) vals = append(vals, spec) } if !hadBNo { if len(flagBno) > 0 { atts = append(atts, att{key: flagBno, val: "1"}) atnm := mkName.Convert(flagBno) spec := mkVSpec(atnm, "1", token.INT) vals = append(vals, spec) } else { atts = nil } } if len(flagTim) > 0 { spec := mkVSpec(flagTim, time.Now().Format(time.RFC3339), token.STRING) vals = append(vals, spec) } res := &ast.File{ Package: 1, Name: &ast.Ident{Name: flagPkg}, Decls: []ast.Decl{ &ast.GenDecl{ Tok: token.CONST, Specs: vals, }, }, } return res, atts } func writeOutFile(filename string, atts []att) (bnoChanged []att) { f, err := os.Create(filename) if err != nil { log.Fatale(err) } defer f.Close() return writeOutput(f, atts) } func writeOutput(wr io.Writer, atts []att) (bnoChanged []att) { vast, bnoChanged := makeAst(atts) fset := token.NewFileSet() format.Node(wr, fset, vast) return bnoChanged } func printTag() { inDir, err := os.Getwd() if err != nil { log.Fatale(err) } inDir, err = filepath.Abs(inDir) if err != nil { log.Fatale(err) } var vFile string for { vFile = filepath.Join(inDir, "VERSION") if _, err := os.Stat(vFile); err == nil { break } tmp := filepath.Dir(inDir) if tmp == inDir { tmp, _ = os.Getwd() log.Fatala("Cannot find VERSION file for `dir`", tmp) } inDir = tmp } atts := readInFile(vFile) checkSemVerNo(atts[0].key, atts[0].val) checkSemVerNo(atts[1].key, atts[1].val) checkSemVerNo(atts[2].key, atts[2].val) fmt.Printf("v%s.%s.%s\n", atts[0].val, atts[1].val, atts[2].val) } func usage() { fmt.Fprintf(os.Stderr, "%s %d.%d.%d-%s+%d (%s)\n", os.Args[0], pack.Major, pack.Minor, pack.Patch, pack.Quality, pack.BuildNo, runtime.Version()) fmt.Fprintln(os.Stderr, "Usage: [flags] [input output]") fmt.Fprintln(os.Stderr, "Flags:") flag.PrintDefaults() } var ( flagBno string flagPkg string flagPfx string flagTim string flagNoSemVer bool flagTag bool ) func main() { flag.StringVar(&flagBno, "bno", "", "use build number attribute") flag.StringVar(&flagPkg, "pkg", "main", "set the package name for generated file") flag.StringVar(&flagPfx, "p", "", "set name prefix for version constants") flag.StringVar(&flagTim, "t", "", "generate timestamp attribute") flag.BoolVar(&flagNoSemVer, "s", false, "disable check for SemVer convetions (https://semver.org)") flag.BoolVar(&flagTag, "tag", false, "Output version tag from VERSION file") flag.Usage = usage flag.Parse() if flagTag { printTag() return } inNm := flag.Arg(0) if len(inNm) == 0 { log.Fatals("missing input file argument") } ouNm := flag.Arg(1) if len(ouNm) == 0 { log.Fatals("missing output file argument") } attls := readInFile(inNm) attls = writeOutFile(ouNm, attls) if attls != nil { writeInFile(inNm, attls) } }