pax_global_header00006660000000000000000000000064136256052100014512gustar00rootroot0000000000000052 comment=c43abd765cf51c06bbcaa5479dc49aab1396989f dh-make-golang-0.3.3/000077500000000000000000000000001362560521000142705ustar00rootroot00000000000000dh-make-golang-0.3.3/.gitignore000066400000000000000000000000271362560521000162570ustar00rootroot00000000000000dh-make-golang _build/ dh-make-golang-0.3.3/.travis.yml000066400000000000000000000006301362560521000164000ustar00rootroot00000000000000dist: bionic language: go go: - 1.11.x - 1.13.x - master arch: - amd64 - arm64 jobs: allow_failures: - go: master fast_finish: true exclude: - arch: arm64 go: 1.11.x cache: directories: - $HOME/.cache/go-build - $HOME/gopath/pkg/mod env: global: - GO111MODULE=on script: - diff -u <(echo -n) <(gofmt -d -s .) - go vet ./... - go test -v -race ./... dh-make-golang-0.3.3/LICENSE000066400000000000000000000027601362560521000153020ustar00rootroot00000000000000Copyright (c) 2015, Michael Stapelberg, Google Inc. and contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of dh-make-golang nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. dh-make-golang-0.3.3/README.md000066400000000000000000000037451362560521000155600ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/Debian/dh-make-golang.svg?branch=master)](https://travis-ci.org/Debian/dh-make-golang) dh-make-golang is a tool to automatically create Debian packaging for Go packages. Its goal is to automate away as much of the work as possible when creating a Debian package for a Go library package or Go program. ## Overview All you need to specify is a Go package name. In your current working directory, a new directory will be created containing a git repository. Inside that repository, you’ll find the Go package’s source code plus the necessary Debian packaging files to build a Debian package. The packaging adheres to the [pkg-go packaging guidelines](https://go-team.pages.debian.net/packaging.html) and hence can be placed alongside the other [team-maintained packages in pkg-go](https://salsa.debian.org/go-team/packages), hosted on Debian’s [salsa](https://wiki.debian.org/Salsa). ## Usage/example For an introductory example, see [this annotated demonstration of how to use dh-make-golang](https://people.debian.org/~stapelberg/2015/07/27/dh-make-golang.html). ## dh-make-golang’s usage of the internet dh-make-golang makes heavy use of online resources to improve the resulting package. In no particular order and depending on where your package is hosted, dh-make-golang may query: * By virtue of using `go get`, the specified Go package and all of its dependencies will be downloaded. This step can quickly cause dozens of megabytes to be transferred, so be careful if you are on a metered internet connection. * The output of [filter-packages.sh](https://github.com/Debian/dh-make-golang/blob/master/filter-packages.sh), hosted on https://people.debian.org/~stapelberg/dh-make-golang/. This is used to figure out whether dependencies are already packaged in Debian, and whether you are about to duplicate somebody else’s work. * GitHub’s API, to get the license, repository creator, description and README for Go packages hosted on GitHub. dh-make-golang-0.3.3/create_salsa_project.go000066400000000000000000000021421362560521000207720ustar00rootroot00000000000000package main import ( "flag" "fmt" "io/ioutil" "log" "net/http" "net/url" "os" ) func execCreateSalsaProject(args []string) { fs := flag.NewFlagSet("create-salsa-project", flag.ExitOnError) fs.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s create-salsa-project \n", os.Args[0]) fmt.Fprintf(os.Stderr, "Example: %s create-salsa-project golang-github-mattn-go-sqlite3\n", os.Args[0]) } if err := fs.Parse(args); err != nil { log.Fatal(err) } if fs.NArg() != 1 { fs.Usage() os.Exit(1) } projectName := fs.Arg(0) // The source code of the corresponding server can be found at: // https://github.com/Debian/pkg-go-tools/tree/master/cmd/pgt-api-server u, _ := url.Parse("https://pgt-api-server.debian.net/v1/createrepo") q := u.Query() q.Set("repo", projectName) u.RawQuery = q.Encode() resp, err := http.Post(u.String(), "", nil) if err != nil { log.Fatal(err) } if got, want := resp.StatusCode, http.StatusOK; got != want { b, _ := ioutil.ReadAll(resp.Body) log.Fatalf("unexpected HTTP status code: got %d, want %d (response: %s)", got, want, string(b)) } } dh-make-golang-0.3.3/description.go000066400000000000000000000121101362560521000171350ustar00rootroot00000000000000package main import ( "bytes" "context" "os/exec" "strings" "github.com/russross/blackfriday" ) func reformatForControl(raw string) string { // Reformat the wrapped description to conform to Debian’s control format. for strings.Contains(raw, "\n\n") { raw = strings.Replace(raw, "\n\n", "\n.\n", -1) } return strings.Replace(raw, "\n", "\n ", -1) } // getDescriptionForGopkg reads from README.md (or equivalent) from GitHub, // intended for the extended description in debian/control. func getLongDescriptionForGopkg(gopkg string) (string, error) { owner, repo, err := findGitHubRepo(gopkg) if err != nil { return "", err } rr, _, err := gitHub.Repositories.GetReadme(context.TODO(), owner, repo, nil) if err != nil { return "", err } content, err := rr.GetContent() if err != nil { return "", err } // Supported filename suffixes are from // https://github.com/github/markup/blob/master/README.md // NOTE(stapelberg): Ideally, we’d use https://github.com/github/markup // itself to render to HTML, then convert HTML to plaintext. That sounds // fairly involved, but it’d be the most correct solution to the problem at // hand. Our current code just knows markdown, which is good enough since // most (Go?) projects in fact use markdown for their README files. if !strings.HasSuffix(rr.GetName(), "md") && !strings.HasSuffix(rr.GetName(), "markdown") && !strings.HasSuffix(rr.GetName(), "mdown") && !strings.HasSuffix(rr.GetName(), "mkdn") { return reformatForControl(strings.TrimSpace(string(content))), nil } output := blackfriday.Markdown([]byte(content), &TextRenderer{}, 0) // Shell out to fmt(1) to line-wrap the output. cmd := exec.Command("fmt") cmd.Stdin = bytes.NewBuffer(output) out, err := cmd.Output() if err != nil { return "", err } return reformatForControl(strings.TrimSpace(string(out))), nil } type TextRenderer struct { } func (options *TextRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string) { out.Write(text) } func (options *TextRenderer) BlockQuote(out *bytes.Buffer, text []byte) { out.Write(text) } func (options *TextRenderer) BlockHtml(out *bytes.Buffer, text []byte) { out.Write(text) } func (options *TextRenderer) Header(out *bytes.Buffer, text func() bool, level int, id string) { text() } func (options *TextRenderer) HRule(out *bytes.Buffer) { out.WriteString("--------------------------------------------------------------------------------\n") } func (options *TextRenderer) List(out *bytes.Buffer, text func() bool, flags int) { text() } func (options *TextRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) { out.WriteString("• ") out.Write(text) } func (options *TextRenderer) Paragraph(out *bytes.Buffer, text func() bool) { out.WriteString("\n") text() out.WriteString("\n") } func (options *TextRenderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) { out.Write(header) out.Write(body) } func (options *TextRenderer) TableRow(out *bytes.Buffer, text []byte) { out.Write(text) } func (options *TextRenderer) TableHeaderCell(out *bytes.Buffer, text []byte, flags int) { out.Write(text) } func (options *TextRenderer) TableCell(out *bytes.Buffer, text []byte, flags int) { out.Write(text) } func (options *TextRenderer) Footnotes(out *bytes.Buffer, text func() bool) { text() } func (options *TextRenderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) { out.WriteString("[") out.Write(name) out.WriteString("]") out.Write(text) } func (options *TextRenderer) TitleBlock(out *bytes.Buffer, text []byte) { } // Span-level callbacks func (options *TextRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) { out.Write(link) } func (options *TextRenderer) CodeSpan(out *bytes.Buffer, text []byte) { out.Write(text) } func (options *TextRenderer) DoubleEmphasis(out *bytes.Buffer, text []byte) { out.Write(text) } func (options *TextRenderer) Emphasis(out *bytes.Buffer, text []byte) { out.Write(text) } func (options *TextRenderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) { out.Write(alt) } func (options *TextRenderer) LineBreak(out *bytes.Buffer) { out.WriteString("\n") } func (options *TextRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { out.Write(content) out.WriteString(" (") out.Write(link) out.WriteString(")") } func (options *TextRenderer) RawHtmlTag(out *bytes.Buffer, tag []byte) { } func (options *TextRenderer) TripleEmphasis(out *bytes.Buffer, text []byte) { out.Write(text) } func (options *TextRenderer) StrikeThrough(out *bytes.Buffer, text []byte) { out.Write(text) } func (options *TextRenderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) { } // Low-level callbacks func (options *TextRenderer) Entity(out *bytes.Buffer, entity []byte) { out.Write(entity) } func (options *TextRenderer) NormalText(out *bytes.Buffer, text []byte) { out.Write(text) } // Header and footer func (options *TextRenderer) DocumentHeader(out *bytes.Buffer) { } func (options *TextRenderer) DocumentFooter(out *bytes.Buffer) { } func (options *TextRenderer) GetFlags() int { return 0 } dh-make-golang-0.3.3/estimate.go000066400000000000000000000114131362560521000164320ustar00rootroot00000000000000package main import ( "flag" "fmt" "go/build" "io/ioutil" "log" "os" "os/exec" "path/filepath" "sort" "strings" "golang.org/x/tools/go/vcs" "golang.org/x/tools/refactor/importgraph" ) func get(gopath, repo string) error { done := make(chan struct{}) defer close(done) go progressSize("go get", filepath.Join(gopath, "src"), done) // As per https://groups.google.com/forum/#!topic/golang-nuts/N5apfenE4m4, // the arguments to “go get” are packages, not repositories. Hence, we // specify “gopkg/...” in order to cover all packages. // As a concrete example, github.com/jacobsa/util is a repository we want // to package into a single Debian package, and using “go get -d // github.com/jacobsa/util” fails because there are no buildable go files // in the top level of that repository. cmd := exec.Command("go", "get", "-d", "-t", repo+"/...") cmd.Stderr = os.Stderr cmd.Env = append([]string{ fmt.Sprintf("GOPATH=%s", gopath), }, passthroughEnv()...) return cmd.Run() } func removeVendor(gopath string) (found bool, _ error) { err := filepath.Walk(filepath.Join(gopath, "src"), func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() { return nil // skip non-directories } if info.Name() != "vendor" { return nil } found = true if err := os.RemoveAll(path); err != nil { return err } return filepath.SkipDir }) return found, err } func estimate(importpath string) error { // construct a separate GOPATH in a temporary directory gopath, err := ioutil.TempDir("", "dh-make-golang") if err != nil { return err } defer os.RemoveAll(gopath) if err := get(gopath, importpath); err != nil { return err } found, err := removeVendor(gopath) if err != nil { return err } if found { // Fetch un-vendored dependencies if err := get(gopath, importpath); err != nil { return err } } // Remove standard lib packages cmd := exec.Command("go", "list", "std") cmd.Stderr = os.Stderr cmd.Env = append([]string{ fmt.Sprintf("GOPATH=%s", gopath), }, passthroughEnv()...) out, err := cmd.Output() if err != nil { return fmt.Errorf("%v: %v", cmd.Args, err) } stdlib := make(map[string]bool) for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { stdlib[line] = true } stdlib["C"] = true // would fail resolving anyway // Filter out all already-packaged ones: golangBinaries, err := getGolangBinaries() if err != nil { return nil } build.Default.GOPATH = gopath forward, _, errors := importgraph.Build(&build.Default) if len(errors) > 0 { lines := make([]string, 0, len(errors)) for importPath, err := range errors { lines = append(lines, fmt.Sprintf("%s: %v", importPath, err)) } return fmt.Errorf("could not load packages: %v", strings.Join(lines, "\n")) } var lines []string seen := make(map[string]bool) rrseen := make(map[string]bool) node := func(importPath string, indent int) { rr, err := vcs.RepoRootForImportPath(importPath, false) if err != nil { log.Printf("Could not determine repo path for import path %q: %v\n", importPath, err) return } if rrseen[rr.Root] { return } rrseen[rr.Root] = true if _, ok := golangBinaries[rr.Root]; ok { return // already packaged in Debian } lines = append(lines, fmt.Sprintf("%s%s", strings.Repeat(" ", indent), rr.Root)) } var visit func(x string, indent int) visit = func(x string, indent int) { if seen[x] { return } seen[x] = true if !stdlib[x] { node(x, indent) } for y := range forward[x] { visit(y, indent+1) } } keys := make([]string, 0, len(forward)) for key := range forward { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { if !strings.HasPrefix(key, importpath) { continue } if seen[key] { continue // already covered in a previous visit call } visit(key, 0) } if len(lines) == 0 { log.Printf("%s is already fully packaged in Debian", importpath) return nil } log.Printf("Bringing %s to Debian requires packaging the following Go packages:", importpath) for _, line := range lines { fmt.Println(line) } return nil } func execEstimate(args []string) { fs := flag.NewFlagSet("estimate", flag.ExitOnError) fs.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s estimate \n", os.Args[0]) fmt.Fprintf(os.Stderr, "Estimates the work necessary to bring into Debian\n"+ "by printing all currently unpacked repositories.\n") fmt.Fprintf(os.Stderr, "Example: %s estimate github.com/Debian/dh-make-golang\n", os.Args[0]) } err := fs.Parse(args) if err != nil { log.Fatal(err) } if fs.NArg() != 1 { fs.Usage() os.Exit(1) } // TODO: support the -git_revision flag if err := estimate(fs.Arg(0)); err != nil { log.Fatal(err) } } dh-make-golang-0.3.3/filter-packages.sh000077500000000000000000000006101362560521000176650ustar00rootroot00000000000000#!/bin/zsh mkdir -p /home/stapelberg/public_html/dh-make-golang TEMPFILE=$(mktemp) wget -q -O- http://httpredir.debian.org/debian/dists/sid/main/binary-amd64/Packages.gz | zgrep '^Package: golang-' | uniq > "${TEMPFILE}" if [ -s "${TEMPFILE}" ]; then chmod o+r "${TEMPFILE}" mv "${TEMPFILE}" /home/stapelberg/public_html/dh-make-golang/binary-amd64-grep-golang else rm "${TEMPFILE}" fi dh-make-golang-0.3.3/go.mod000066400000000000000000000006301362560521000153750ustar00rootroot00000000000000module github.com/Debian/dh-make-golang go 1.12 require ( github.com/google/go-github/v28 v28.1.1 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 github.com/mattn/go-isatty v0.0.10 github.com/russross/blackfriday v1.5.2 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 golang.org/x/sync v0.0.0-20190423024810-112230192c58 golang.org/x/tools v0.0.0-20190731163215-a81e99d7481f ) dh-make-golang-0.3.3/go.sum000066400000000000000000000053601362560521000154270ustar00rootroot00000000000000github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190731163215-a81e99d7481f h1:vwy91pya0J00SsPf35DrwOUOe/76iE4RbegH6XWNn9I= golang.org/x/tools v0.0.0-20190731163215-a81e99d7481f/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= dh-make-golang-0.3.3/main.go000066400000000000000000000037521362560521000155520ustar00rootroot00000000000000package main import ( "fmt" "os" "github.com/google/go-github/v28/github" "github.com/gregjones/httpcache" ) const program = "dh-make-golang" var ( gitHub *github.Client ) func usage() { fmt.Fprintf(os.Stderr, "%s\n", buildVersionString()) fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "%s is a tool that converts Go packages into Debian package source.\n", program) fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "Usage:\n\t%s [globalflags] [flags] \n", program) fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "%s commands:\n", program) fmt.Fprintf(os.Stderr, "\tmake\t\t\tcreate a Debian package\n") fmt.Fprintf(os.Stderr, "\tsearch\t\t\tsearch Debian for already-existing packages\n") fmt.Fprintf(os.Stderr, "\testimate\t\testimate the amount of work for a package\n") fmt.Fprintf(os.Stderr, "\tcreate-salsa-project\tcreate a project for hosting Debian packaging\n") fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "For backwards compatibility, when no command is specified,\nthe make command is executed.\n") fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "To learn more about a command, run \"%s -help\",\ne.g. \"%s make -help\"\n", program, program) fmt.Fprintf(os.Stderr, "\n") } func main() { transport := github.BasicAuthTransport{ Username: os.Getenv("GITHUB_USERNAME"), Password: os.Getenv("GITHUB_PASSWORD"), OTP: os.Getenv("GITHUB_OTP"), Transport: httpcache.NewMemoryCacheTransport(), } gitHub = github.NewClient(transport.Client()) // Retrieve args and Shift binary name off argument list. args := os.Args[1:] // Retrieve command name as first argument. cmd := "" if len(args) > 0 { cmd = args[0] } switch cmd { case "help": usage() case "search": execSearch(args[1:]) case "create-salsa-project": execCreateSalsaProject(args[1:]) case "estimate": execEstimate(args[1:]) case "make": execMake(args[1:], nil) default: // redirect -help to the global usage execMake(args, usage) } } dh-make-golang-0.3.3/make.go000066400000000000000000000727661362560521000155560ustar00rootroot00000000000000package main import ( "flag" "fmt" "io" "io/ioutil" "log" "net/http" "net/url" "os" "os/exec" "os/user" "path/filepath" "strings" "golang.org/x/net/publicsuffix" "golang.org/x/sync/errgroup" "golang.org/x/tools/go/vcs" ) type packageType int const ( typeGuess packageType = iota typeLibrary typeProgram typeLibraryProgram typeProgramLibrary ) var wrapAndSort string func passthroughEnv() []string { var relevantVariables = []string{ "HOME", "PATH", "HTTP_PROXY", "http_proxy", "HTTPS_PROXY", "https_proxy", "ALL_PROXY", "all_proxy", "NO_PROXY", "no_proxy", "GIT_PROXY_COMMAND", "GIT_HTTP_PROXY_AUTHMETHOD", } var result []string for _, variable := range relevantVariables { if value, ok := os.LookupEnv(variable); ok { result = append(result, fmt.Sprintf("%s=%s", variable, value)) } } return result } func findVendorDirs(dir string) ([]string, error) { var vendorDirs []string err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info != nil && !info.IsDir() { return nil // nothing to do for anything but directories } if info.Name() == ".git" || info.Name() == ".hg" || info.Name() == ".bzr" { return filepath.SkipDir } if info.Name() == "vendor" { rel, err := filepath.Rel(dir, path) if err != nil { return err } vendorDirs = append(vendorDirs, rel) } return nil }) return vendorDirs, err } func downloadFile(filename, url string) error { dst, err := os.Create(filename) if err != nil { return err } defer dst.Close() resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != 200 { return fmt.Errorf(resp.Status) } _, err = io.Copy(dst, resp.Body) if err != nil { return err } return nil } // upstream describes the upstream repo we are about to package. type upstream struct { rr *vcs.RepoRoot tarPath string // path to the downloaded or generated orig tarball tempfile compression string // compression method, either "gz" or "xz" version string // Debian package upstream version number, e.g. 0.0~git20180204.1d24609 commitIsh string // commit-ish corresponding to upstream version to be packaged remote string // git remote, set to short hostname if upstream git history is included firstMain string // import path of the first main package within repo, if any vendorDirs []string // all vendor sub directories, relative to the repo directory repoDeps []string // the repository paths of all dependencies (e.g. github.com/zyedidia/glob) hasGodeps bool // whether the Godeps/_workspace directory exists hasRelease bool // whether any release tags exist, for debian/watch isRelease bool // whether what we end up packaging is a tagged release } func (u *upstream) get(gopath, repo, rev string) error { done := make(chan struct{}) defer close(done) go progressSize("go get", filepath.Join(gopath, "src"), done) rr, err := vcs.RepoRootForImportPath(repo, false) if err != nil { return err } u.rr = rr dir := filepath.Join(gopath, "src", rr.Root) if rev != "" { return rr.VCS.CreateAtRev(dir, rr.Repo, rev) } return rr.VCS.Create(dir, rr.Repo) } func (u *upstream) tarballFromHoster() error { var tarURL string repo := strings.TrimSuffix(u.rr.Repo, ".git") repoU, err := url.Parse(repo) if err != nil { return err } switch repoU.Host { case "github.com": tarURL = fmt.Sprintf("%s/archive/v%s.tar.%s", repo, u.version, u.compression) case "gitlab.com": parts := strings.Split(repoU.Path, "/") if len(parts) < 3 { return fmt.Errorf("Incomplete repo URL: %s", u.rr.Repo) } project := parts[2] tarURL = fmt.Sprintf("%s/-/archive/v%[3]s/%[2]s-%s.tar.%s", repo, project, u.version, u.compression) default: return fmt.Errorf("Unsupported hoster") } done := make(chan struct{}) go progressSize("Download", u.tarPath, done) log.Printf("Downloading %s", tarURL) err = downloadFile(u.tarPath, tarURL) close(done) return err } func (u *upstream) tar(gopath, repo string) error { f, err := ioutil.TempFile("", "dh-make-golang") if err != nil { return err } u.tarPath = f.Name() f.Close() if u.isRelease { if u.hasGodeps { log.Printf("Godeps/_workspace exists, not downloading tarball from hoster.") } else { u.compression = "gz" err := u.tarballFromHoster() if err != nil && err.Error() == "Unsupported hoster" { log.Printf("INFO: Hoster does not provide release tarball\n") } else { return err } } } u.compression = "xz" base := filepath.Base(repo) log.Printf("Generating temp tarball as %q\n", u.tarPath) dir := filepath.Dir(repo) cmd := exec.Command( "tar", "cJf", u.tarPath, "--exclude=.git", "--exclude=Godeps/_workspace", "--exclude="+base+"/debian", base) cmd.Dir = filepath.Join(gopath, "src", dir) cmd.Stderr = os.Stderr return cmd.Run() } // findMains finds main packages within the repo (useful to auto-detect the // package type). func (u *upstream) findMains(gopath, repo string) error { cmd := exec.Command("go", "list", "-f", "{{.ImportPath}} {{.Name}}", repo+"/...") cmd.Stderr = os.Stderr cmd.Env = append([]string{ fmt.Sprintf("GOPATH=%s", gopath), }, passthroughEnv()...) out, err := cmd.Output() if err != nil { return fmt.Errorf("%v: %v", cmd.Args, err) } for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { if strings.Contains(line, "/vendor/") || strings.Contains(line, "/Godeps/") || strings.Contains(line, "/samples/") || strings.Contains(line, "/examples/") || strings.Contains(line, "/example/") { continue } if strings.HasSuffix(line, " main") { u.firstMain = strings.TrimSuffix(line, " main") break } } return nil } func (u *upstream) findDependencies(gopath, repo string) error { log.Printf("Determining dependencies\n") cmd := exec.Command("go", "list", "-f", "{{join .Imports \"\\n\"}}\n{{join .TestImports \"\\n\"}}\n{{join .XTestImports \"\\n\"}}", repo+"/...") cmd.Stderr = os.Stderr cmd.Env = append([]string{ fmt.Sprintf("GOPATH=%s", gopath), }, passthroughEnv()...) out, err := cmd.Output() if err != nil { return fmt.Errorf("%v: %v", cmd.Args, err) } godependencies := make(map[string]bool) for _, p := range strings.Split(strings.TrimSpace(string(out)), "\n") { if p == "" { continue // skip separators between import types } // Strip packages that are included in the repository we are packaging. if strings.HasPrefix(p, repo+"/") || p == repo { continue } if p == "C" { // TODO: maybe parse the comments to figure out C deps from pkg-config files? } else { godependencies[p] = true } } if len(godependencies) == 0 { return nil } // Remove all packages which are in the standard lib. cmd = exec.Command("go", "list", "std") cmd.Dir = filepath.Join(gopath, "src", repo) cmd.Stderr = os.Stderr cmd.Env = append([]string{ fmt.Sprintf("GOPATH=%s", gopath), }, passthroughEnv()...) out, err = cmd.Output() if err != nil { return fmt.Errorf("%v: %v", cmd.Args, err) } for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { delete(godependencies, line) } // Resolve all packages to the root of their repository. roots := make(map[string]bool) for dep := range godependencies { rr, err := vcs.RepoRootForImportPath(dep, false) if err != nil { log.Printf("Could not determine repo path for import path %q: %v\n", dep, err) continue } roots[rr.Root] = true } u.repoDeps = make([]string, 0, len(godependencies)) for root := range roots { u.repoDeps = append(u.repoDeps, root) } return nil } func makeUpstreamSourceTarball(repo, revision string, forcePrerelease bool) (*upstream, error) { gopath, err := ioutil.TempDir("", "dh-make-golang") if err != nil { return nil, err } defer os.RemoveAll(gopath) repoDir := filepath.Join(gopath, "src", repo) var u upstream log.Printf("Downloading %q\n", repo+"/...") if err := u.get(gopath, repo, revision); err != nil { return nil, err } // Verify early this repository uses git (we call pkgVersionFromGit later): if _, err := os.Stat(filepath.Join(repoDir, ".git")); os.IsNotExist(err) { return nil, fmt.Errorf("Not a git repository, dh-make-golang currently only supports git") } if _, err := os.Stat(filepath.Join(repoDir, "debian")); err == nil { log.Printf("WARNING: ignoring debian/ directory that came with the upstream sources\n") } u.vendorDirs, err = findVendorDirs(repoDir) if err != nil { return nil, err } if len(u.vendorDirs) > 0 { log.Printf("Deleting upstream vendor/ directories") for _, dir := range u.vendorDirs { if err := os.RemoveAll(filepath.Join(repoDir, dir)); err != nil { return nil, err } } } if _, err := os.Stat(filepath.Join(repoDir, "Godeps", "_workspace")); !os.IsNotExist(err) { log.Println("Godeps/_workspace detected") u.hasGodeps = true } log.Printf("Determining upstream version number\n") u.version, err = pkgVersionFromGit(repoDir, &u, forcePrerelease) if err != nil { return nil, err } log.Printf("Package version is %q\n", u.version) if err := u.findMains(gopath, repo); err != nil { return nil, err } if err := u.findDependencies(gopath, repo); err != nil { return nil, err } if err := u.tar(gopath, repo); err != nil { return nil, err } return &u, nil } func runGitCommandIn(dir string, arg ...string) error { cmd := exec.Command("git", arg...) cmd.Dir = dir cmd.Stderr = os.Stderr return cmd.Run() } func createGitRepository(debsrc, gopkg, orig string, u *upstream, includeUpstreamHistory, allowUnknownHoster, dep14, pristineTar bool) (string, error) { wd, err := os.Getwd() if err != nil { return "", err } dir := filepath.Join(wd, debsrc) if err := os.Mkdir(dir, 0755); err != nil { return "", err } if err := runGitCommandIn(dir, "init"); err != nil { return dir, err } if dep14 { if err := runGitCommandIn(dir, "checkout", "-q", "-b", "debian/sid"); err != nil { return dir, err } } // Set repository options if debianName := getDebianName(); debianName != "TODO" { if err := runGitCommandIn(dir, "config", "user.name", debianName); err != nil { return dir, err } } if debianEmail := getDebianEmail(); debianEmail != "TODO" { if err := runGitCommandIn(dir, "config", "user.email", debianEmail); err != nil { return dir, err } } if err := runGitCommandIn(dir, "config", "push.default", "matching"); err != nil { return dir, err } // [remote "origin"] originURL := "git@salsa.debian.org:go-team/packages/" + debsrc + ".git" log.Printf("Adding remote \"origin\" with URL %q\n", originURL) if err := runGitCommandIn(dir, "remote", "add", "origin", originURL); err != nil { return dir, err } if err := runGitCommandIn(dir, "config", "--add", "remote.origin.push", "+refs/heads/*:refs/heads/*"); err != nil { return dir, err } if err := runGitCommandIn(dir, "config", "--add", "remote.origin.push", "+refs/tags/*:refs/tags/*"); err != nil { return dir, err } // Preconfigure branches var debianBranch string if dep14 { debianBranch = "debian/sid" } else { debianBranch = "master" } branches := []string{debianBranch, "upstream"} if pristineTar { branches = append(branches, "pristine-tar") } for _, branch := range branches { if err := runGitCommandIn(dir, "config", "branch."+branch+".remote", "origin"); err != nil { return dir, err } if err := runGitCommandIn(dir, "config", "branch."+branch+".merge", "refs/heads/"+branch); err != nil { return dir, err } } if includeUpstreamHistory { u.remote, err = shortHostName(gopkg, allowUnknownHoster) if err != nil { return dir, fmt.Errorf("Unable to fetch upstream history: %q", err) } log.Printf("Adding remote %q with URL %q\n", u.remote, u.rr.Repo) if err := runGitCommandIn(dir, "remote", "add", u.remote, u.rr.Repo); err != nil { return dir, err } log.Printf("Running \"git fetch %s\"\n", u.remote) if err := runGitCommandIn(dir, "fetch", u.remote); err != nil { return dir, err } } // Import upstream orig tarball arg := []string{"import-orig", "--no-interactive"} if dep14 { arg = append(arg, "--debian-branch=debian/sid") } if pristineTar { arg = append(arg, "--pristine-tar") } if includeUpstreamHistory { arg = append(arg, "--upstream-vcs-tag="+u.commitIsh) } arg = append(arg, filepath.Join(wd, orig)) cmd := exec.Command("gbp", arg...) cmd.Dir = dir cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return dir, err } { f, err := os.OpenFile(filepath.Join(dir, ".gitignore"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return dir, err } // Beginning newline in case the file already exists and lacks a newline // (not all editors enforce a newline at the end of the file): if _, err := f.Write([]byte("\n.pc\n")); err != nil { return dir, err } if err := f.Close(); err != nil { return dir, err } } if err := runGitCommandIn(dir, "add", ".gitignore"); err != nil { return dir, err } if err := runGitCommandIn(dir, "commit", "-m", "Ignore quilt dir .pc via .gitignore"); err != nil { return dir, err } return dir, nil } // normalize program/source name into Debian standard[1] // https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Source // Package names (both source and binary, see Package, Section 5.6.7) must // consist only of lower case letters (a-z), digits (0-9), plus (+) and minus // (-) signs, and periods (.). They must be at least two characters long and // must start with an alphanumeric character. func normalizeDebianProgramName(str string) string { lowerDigitPlusMinusDot := func(r rune) rune { switch { case r >= 'a' && r <= 'z' || '0' <= r && r <= '9': return r case r >= 'A' && r <= 'Z': return r + ('a' - 'A') case r == '.' || r == '+' || r == '-': return r case r == '_': return '-' } return -1 } safe := strings.Trim(strings.Map(lowerDigitPlusMinusDot, str), "-") if len(safe) < 2 { return "TODO" } return safe } func shortHostName(gopkg string, allowUnknownHoster bool) (host string, err error) { knownHosts := map[string]string{ // keep the list in alphabetical order "bazil.org": "bazil", "bitbucket.org": "bitbucket", "blitiri.com.ar": "blitiri", "cloud.google.com": "googlecloud", "code.google.com": "googlecode", "git.sr.ht": "sourcehut", "github.com": "github", "gitlab.com": "gitlab", "go.uber.org": "uber", "go4.org": "go4", "gocloud.dev": "gocloud", "golang.org": "golang", "google.golang.org": "google", "gopkg.in": "gopkg", "honnef.co": "honnef", "howett.net": "howett", "k8s.io": "k8s", "pault.ag": "pault", "salsa.debian.org": "debian", "sigs.k8s.io": "k8s-sigs", } parts := strings.Split(gopkg, "/") fqdn := parts[0] if host, ok := knownHosts[fqdn]; ok { return host, nil } if !allowUnknownHoster { return "", fmt.Errorf("unknown hoster %q", fqdn) } suffix, _ := publicsuffix.PublicSuffix(fqdn) host = fqdn[:len(fqdn)-len(suffix)-len(".")] log.Printf("WARNING: Using %q as canonical hostname for %q. If that is not okay, please file a bug against %s.\n", host, fqdn, os.Args[0]) return host, nil } // debianNameFromGopkg maps a Go package repo path to a Debian package name, // e.g. "golang.org/x/text" → "golang-golang-x-text". // This follows https://fedoraproject.org/wiki/PackagingDrafts/Go#Package_Names func debianNameFromGopkg(gopkg string, t packageType, allowUnknownHoster bool) string { parts := strings.Split(gopkg, "/") if t == typeProgram || t == typeProgramLibrary { return normalizeDebianProgramName(parts[len(parts)-1]) } host, err := shortHostName(gopkg, allowUnknownHoster) if err != nil { log.Fatalf("Cannot derive Debian package name: %v. See -help output for -allow_unknown_hoster\n", err) } parts[0] = host return strings.Trim("golang-"+strings.ToLower(strings.Replace(strings.Join(parts, "-"), "_", "-", -1)), "-") } func getDebianName() string { if name := strings.TrimSpace(os.Getenv("DEBFULLNAME")); name != "" { return name } if name := strings.TrimSpace(os.Getenv("DEBNAME")); name != "" { return name } if u, err := user.Current(); err == nil && u.Name != "" { return u.Name } return "TODO" } func getDebianEmail() string { if email := strings.TrimSpace(os.Getenv("DEBEMAIL")); email != "" { return email } mailname, err := ioutil.ReadFile("/etc/mailname") // By default, /etc/mailname contains "debian" which is not useful; check for ".". if err == nil && strings.Contains(string(mailname), ".") { if u, err := user.Current(); err == nil && u.Username != "" { return u.Username + "@" + strings.TrimSpace(string(mailname)) } } return "TODO" } func writeITP(gopkg, debsrc, debversion string) (string, error) { itpname := fmt.Sprintf("itp-%s.txt", debsrc) f, err := os.Create(itpname) if err != nil { return itpname, err } defer f.Close() // TODO: memoize license, _, err := getLicenseForGopkg(gopkg) if err != nil { log.Printf("Could not determine license for %q: %v\n", gopkg, err) license = "TODO" } author, _, err := getAuthorAndCopyrightForGopkg(gopkg) if err != nil { log.Printf("Could not determine author for %q: %v\n", gopkg, err) author = "TODO" } description, err := getDescriptionForGopkg(gopkg) if err != nil { log.Printf("Could not determine description for %q: %v\n", gopkg, err) description = "TODO" } fmt.Fprintf(f, "From: %q <%s>\n", getDebianName(), getDebianEmail()) fmt.Fprintf(f, "To: submit@bugs.debian.org\n") fmt.Fprintf(f, "Subject: ITP: %s -- %s\n", debsrc, description) fmt.Fprintf(f, "Content-Type: text/plain; charset=utf-8\n") fmt.Fprintf(f, "Content-Transfer-Encoding: 8bit\n") fmt.Fprintf(f, "X-Debbugs-CC: debian-devel@lists.debian.org, debian-go@lists.debian.org\n") fmt.Fprintf(f, "\n") fmt.Fprintf(f, "Package: wnpp\n") fmt.Fprintf(f, "Severity: wishlist\n") fmt.Fprintf(f, "Owner: %s <%s>\n", getDebianName(), getDebianEmail()) fmt.Fprintf(f, "\n") fmt.Fprintf(f, "* Package name : %s\n", debsrc) fmt.Fprintf(f, " Version : %s\n", debversion) fmt.Fprintf(f, " Upstream Author : %s\n", author) fmt.Fprintf(f, "* URL : %s\n", getHomepageForGopkg(gopkg)) fmt.Fprintf(f, "* License : %s\n", license) fmt.Fprintf(f, " Programming Lang: Go\n") fmt.Fprintf(f, " Description : %s\n", description) fmt.Fprintf(f, "\n") longdescription, err := getLongDescriptionForGopkg(gopkg) if err != nil { log.Printf("Could not determine long description for %q: %v\n", gopkg, err) longdescription = "TODO: long description" } fmt.Fprintf(f, " %s\n", longdescription) fmt.Fprintf(f, "\n") fmt.Fprintf(f, "TODO: perhaps reasoning\n") return itpname, nil } func copyFile(src, dest string) error { input, err := os.Open(src) if err != nil { return err } defer input.Close() output, err := os.Create(dest) if err != nil { return err } if _, err := io.Copy(output, input); err != nil { return err } return output.Close() } func execMake(args []string, usage func()) { fs := flag.NewFlagSet("make", flag.ExitOnError) if usage != nil { fs.Usage = usage } else { fs.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s [make] [FLAG]... \n", os.Args[0]) fmt.Fprintf(os.Stderr, "Example: %s make golang.org/x/oauth2\n", os.Args[0]) fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "\"%s make\" downloads the specified Go package from the Internet,\nand creates new files and directories in the current working directory.\n", os.Args[0]) fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "Flags:\n") fs.PrintDefaults() } } var gitRevision string fs.StringVar(&gitRevision, "git_revision", "", "git revision (see gitrevisions(7)) of the specified Go package\n"+ "to check out, defaulting to the default behavior of git clone.\n"+ "Useful in case you do not want to package e.g. current HEAD.") var allowUnknownHoster bool fs.BoolVar(&allowUnknownHoster, "allow_unknown_hoster", false, "The pkg-go naming conventions use a canonical identifier for\n"+ "the hostname (see https://go-team.pages.debian.net/packaging.html),\n"+ "and the mapping is hardcoded into dh-make-golang.\n"+ "In case you want to package a Go package living on an unknown hoster,\n"+ "you may set this flag to true and double-check that the resulting\n"+ "package name is sane. Contact pkg-go if unsure.") var dep14 bool fs.BoolVar(&dep14, "dep14", true, "Follow DEP-14 branch naming and use debian/sid (instead of master)\n"+ "as the default debian-branch.") var pristineTar bool fs.BoolVar(&pristineTar, "pristine-tar", false, "Keep using a pristine-tar branch as in the old workflow.\n"+ "Strongly discouraged, see \"pristine-tar considered harmful\"\n"+ "https://michael.stapelberg.ch/posts/2018-01-28-pristine-tar/\n"+ "and the \"Drop pristine-tar branches\" section at\n"+ "https://go-team.pages.debian.net/workflow-changes.html") var forcePrerelease bool fs.BoolVar(&forcePrerelease, "force-prerelease", false, "Package @master or @tip instead of the latest tagged version") var pkgTypeString string fs.StringVar(&pkgTypeString, "type", "", "Set package type, one of:\n"+ ` * "library" (aliases: "lib", "l", "dev")`+"\n"+ ` * "program" (aliases: "prog", "p")`+"\n"+ ` * "library+program" (aliases: "lib+prog", "l+p", "both")`+"\n"+ ` * "program+library" (aliases: "prog+lib", "p+l", "combined")`) var includeUpstreamHistory bool fs.BoolVar(&includeUpstreamHistory, "upstream-git-history", true, "Include upstream git history (Debian pkg-go team new workflow).\n"+ "New in dh-make-golang 0.3.0, currently experimental.") fs.StringVar(&wrapAndSort, "wrap-and-sort", "a", "Set how the various multi-line fields in debian/control are formatted.\n"+ "Valid values are \"a\", \"at\" and \"ast\", see wrap-and-sort(1) man page\n"+ "for more information.") // ==================================================================== // // Start actual make routine // // ==================================================================== log.Printf("Starting %q", buildVersionString()) err := fs.Parse(args) if err != nil { log.Fatal(err) } if fs.NArg() < 1 { fs.Usage() os.Exit(1) } gitRevision = strings.TrimSpace(gitRevision) gopkg := fs.Arg(0) // Ensure the specified argument is a Go package import path. rr, err := vcs.RepoRootForImportPath(gopkg, false) if err != nil { log.Fatalf("Verifying arguments: %v — did you specify a Go package import path?", err) } if gopkg != rr.Root { log.Printf("Continuing with repository root %q instead of specified import path %q (repositories are the unit of packaging in Debian)", rr.Root, gopkg) gopkg = rr.Root } // Set default source and binary package names. // Note that debsrc may change depending on the actual package type. debsrc := debianNameFromGopkg(gopkg, typeLibrary, allowUnknownHoster) debLib := debsrc + "-dev" debProg := debianNameFromGopkg(gopkg, typeProgram, allowUnknownHoster) var pkgType packageType switch strings.TrimSpace(pkgTypeString) { case "", "guess": pkgType = typeGuess case "library", "lib", "l", "dev": pkgType = typeLibrary case "program", "prog", "p": pkgType = typeProgram case "library+program", "lib+prog", "l+p", "both": // Example packages: golang-github-alecthomas-chroma, // golang-github-tdewolff-minify, golang-github-spf13-viper pkgType = typeLibraryProgram case "program+library", "prog+lib", "p+l", "combined": // Example package: hugo pkgType = typeProgramLibrary default: log.Fatalf("-type=%q not recognized, aborting\n", pkgTypeString) } switch strings.TrimSpace(wrapAndSort) { case "a": // Current default, also what "cme fix dpkg" generates wrapAndSort = "a" case "at", "ta": // -t, --trailing-comma, preferred by Martina Ferrari // and currently used in quite a few packages wrapAndSort = "at" case "ast", "ats", "sat", "sta", "tas", "tsa": // -s, --short-indent too, proposed by Guillem Jover wrapAndSort = "ast" default: log.Fatalf("%q is not a valid value for -wrap-and-sort, aborting.", wrapAndSort) } if pkgType != typeGuess { debsrc = debianNameFromGopkg(gopkg, pkgType, allowUnknownHoster) if _, err := os.Stat(debsrc); err == nil { log.Fatalf("Output directory %q already exists, aborting\n", debsrc) } } if strings.ToLower(gopkg) != gopkg { // Without -git_revision, specifying the package name in the wrong case // will lead to two checkouts, i.e. wasting bandwidth. With // -git_revision, packaging might fail. // // In case it turns out that Go package names should never contain any // uppercase letters, we can just auto-convert the argument. log.Printf("WARNING: Go package names are case-sensitive. Did you really mean %q instead of %q?\n", gopkg, strings.ToLower(gopkg)) } var ( eg errgroup.Group golangBinaries map[string]string // map[goImportPath]debianBinaryPackage ) // TODO: also check whether there already is a git repository on salsa. eg.Go(func() error { var err error golangBinaries, err = getGolangBinaries() return err }) u, err := makeUpstreamSourceTarball(gopkg, gitRevision, forcePrerelease) if err != nil { log.Fatalf("Could not create a tarball of the upstream source: %v\n", err) } if pkgType == typeGuess { if u.firstMain != "" { log.Printf("Assuming you are packaging a program (because %q defines a main package), use -type to override\n", u.firstMain) pkgType = typeProgram debsrc = debianNameFromGopkg(gopkg, pkgType, allowUnknownHoster) } else { pkgType = typeLibrary } } if _, err := os.Stat(debsrc); err == nil { log.Fatalf("Output directory %q already exists, aborting\n", debsrc) } if err := eg.Wait(); err != nil { log.Printf("Could not check for existing Go packages in Debian: %v", err) } if debbin, ok := golangBinaries[gopkg]; ok { log.Printf("WARNING: A package called %q is already in Debian! See https://tracker.debian.org/pkg/%s\n", debbin, debbin) } orig := fmt.Sprintf("%s_%s.orig.tar.%s", debsrc, u.version, u.compression) log.Printf("Moving tempfile to %q\n", orig) // We need to copy the file, merely renaming is not enough since the file // might be on a different filesystem (/tmp often is a tmpfs). if err := copyFile(u.tarPath, orig); err != nil { log.Fatalf("Could not rename orig tarball from %q to %q: %v\n", u.tarPath, orig, err) } if err := os.Remove(u.tarPath); err != nil { log.Printf("Could not remove tempfile %q: %v\n", u.tarPath, err) } debversion := u.version + "-1" dir, err := createGitRepository(debsrc, gopkg, orig, u, includeUpstreamHistory, allowUnknownHoster, dep14, pristineTar) if err != nil { log.Fatalf("Could not create git repository: %v\n", err) } debdependencies := make([]string, 0, len(u.repoDeps)) for _, dep := range u.repoDeps { if len(golangBinaries) == 0 { // fall back to heuristic debdependencies = append(debdependencies, debianNameFromGopkg(dep, typeLibrary, allowUnknownHoster)+"-dev") continue } bin, ok := golangBinaries[dep] if !ok { log.Printf("Build-Dependency %q is not yet available in Debian, or has not yet been converted to use XS-Go-Import-Path in debian/control", dep) continue } debdependencies = append(debdependencies, bin) } if err := writeTemplates(dir, gopkg, debsrc, debLib, debProg, debversion, pkgType, debdependencies, u, dep14, pristineTar); err != nil { log.Fatalf("Could not create debian/ from templates: %v\n", err) } itpname, err := writeITP(gopkg, debsrc, debversion) if err != nil { log.Fatalf("Could not write ITP email: %v\n", err) } log.Println("Done!") fmt.Printf("\n") fmt.Printf("Packaging successfully created in %s\n", dir) fmt.Printf(" Source: %s\n", debsrc) switch pkgType { case typeLibrary: fmt.Printf(" Binary: %s\n", debLib) case typeProgram: fmt.Printf(" Binary: %s\n", debProg) case typeLibraryProgram: fmt.Printf(" Binary: %s\n", debLib) fmt.Printf(" Binary: %s\n", debProg) case typeProgramLibrary: fmt.Printf(" Binary: %s\n", debProg) fmt.Printf(" Binary: %s\n", debLib) } fmt.Printf("\n") fmt.Printf("Resolve all TODOs in %s, then email it out:\n", itpname) fmt.Printf(" /usr/sbin/sendmail -t < %s\n", itpname) fmt.Printf("\n") fmt.Printf("Resolve all the TODOs in debian/, find them using:\n") fmt.Printf(" grep -r TODO debian\n") fmt.Printf("\n") fmt.Printf("To build the package, commit the packaging and use gbp buildpackage:\n") fmt.Printf(" git add debian && git commit -a -m 'Initial packaging'\n") fmt.Printf(" gbp buildpackage --git-pbuilder\n") fmt.Printf("\n") fmt.Printf("To create the packaging git repository on salsa, use:\n") fmt.Printf(" dh-make-golang create-salsa-project %s\n", debsrc) fmt.Printf("\n") fmt.Printf("Once you are happy with your packaging, push it to salsa using:\n") fmt.Printf(" gbp push\n") fmt.Printf("\n") if includeUpstreamHistory { fmt.Printf("NOTE: Full upstream git history has been included as per pkg-go team's\n") fmt.Printf(" new workflow. This feature is new and somewhat experimental,\n") fmt.Printf(" and all feedback are welcome!\n") fmt.Printf(" (For old behavior, use --upstream-git-history=false)\n") fmt.Printf("\n") fmt.Printf("The upstream git history is being tracked with the remote named %q.\n", u.remote) fmt.Printf("To upgrade to the latest upstream version, you may use something like:\n") fmt.Printf(" git fetch %-15v # note the latest tag or commit-ish\n", u.remote) fmt.Printf(" uscan --report-status # check we get the same tag or commit-ish\n") fmt.Printf(" gbp import-orig --sign-tags --uscan --upstream-vcs-tag=\n") fmt.Printf("\n") } } dh-make-golang-0.3.3/make_test.go000066400000000000000000000026551362560521000166030ustar00rootroot00000000000000package main import ( "testing" ) var shortName = []struct { in string out string }{ {"", "TODO"}, {"d", "TODO"}, {"d--", "TODO"}, } func TestAcceptInput(t *testing.T) { for _, tt := range shortName { in := normalizeDebianProgramName(tt.in) if in != tt.out { t.Errorf("userInput(%q) => %q, want %q", tt.in, in, tt.out) } } } var miscName = []struct { in string out string }{ {"dh-make-golang", "dh-make-golang"}, {"DH-make-golang", "dh-make-golang"}, {"dh_make_golang", "dh-make-golang"}, {"dh_make*go&3*@@", "dh-makego3"}, {"7h_make*go&3*@@", "7h-makego3"}, {"7h_make*go&3*.@", "7h-makego3."}, {"7h_make*go+3*.@", "7h-makego+3."}, } func TestNormalizeDebianProgramName(t *testing.T) { for _, tt := range miscName { s := normalizeDebianProgramName(tt.in) if s != tt.out { t.Errorf("normalizeDebianProgramName(%q) => %q, want %q", tt.in, s, tt.out) } } } var nameFromGoPkg = []struct { in string t packageType out string }{ {"github.com/Debian/dh-make-golang", typeProgram, "dh-make-golang"}, {"github.com/Debian/DH-make-golang", typeGuess, "golang-github-debian-dh-make-golang"}, {"github.com/Debian/dh_make_golang", typeGuess, "golang-github-debian-dh-make-golang"}, } func TestDebianNameFromGopkg(t *testing.T) { for _, tt := range nameFromGoPkg { s := debianNameFromGopkg(tt.in, tt.t, false) if s != tt.out { t.Errorf("debianNameFromGopkg(%q) => %q, want %q", tt.in, s, tt.out) } } } dh-make-golang-0.3.3/metadata.go000066400000000000000000000125641362560521000164070ustar00rootroot00000000000000package main import ( "context" "fmt" "net/http" "regexp" "strings" "golang.org/x/net/html" ) // To update, use: // curl -s https://api.github.com/licenses | jq '.[].key' // then compare with https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#license-specification var githubLicenseToDebianLicense = map[string]string{ //"agpl-3.0" (not in debian?) "apache-2.0": "Apache-2.0", "artistic-2.0": "Artistic-2.0", "bsd-2-clause": "BSD-2-clause", "bsd-3-clause": "BSD-3-clause", "cc0-1.0": "CC0-1.0", //"epl-1.0" (eclipse public license) "gpl-2.0": "GPL-2.0", // TODO: is this GPL-2.0+? "gpl-3.0": "GPL-3.0", "isc": "ISC", "lgpl-2.1": "LGPL-2.1", "lgpl-3.0": "LGPL-3.0", "mit": "Expat", "mpl-2.0": "MPL-2.0", // include in base-files >= 9.9 //"unlicense" (not in debian) } var debianLicenseText = map[string]string{ "Apache-2.0": ` Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at . http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. . On Debian systems, the complete text of the Apache version 2.0 license can be found in "/usr/share/common-licenses/Apache-2.0". `, "MPL-2.0": ` This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. . On Debian systems, the complete text of the MPL-2.0 license can be found in "/usr/share/common-licenses/MPL-2.0". `, } var githubRegexp = regexp.MustCompile(`github\.com/([^/]+/[^/]+)`) func findGitHubOwnerRepo(gopkg string) (string, error) { if strings.HasPrefix(gopkg, "github.com/") { return strings.TrimPrefix(gopkg, "github.com/"), nil } resp, err := http.Get("https://" + gopkg + "?go-get=1") if err != nil { return "", err } defer resp.Body.Close() z := html.NewTokenizer(resp.Body) for { tt := z.Next() if tt == html.ErrorToken { return "", fmt.Errorf("%q is not on GitHub", gopkg) } token := z.Token() if token.Data != "meta" { continue } var meta struct { name, content string } for _, attr := range token.Attr { if attr.Key == "name" { meta.name = attr.Val } if attr.Key == "content" { meta.content = attr.Val } } match := func(name string, length int) string { if f := strings.Fields(meta.content); meta.name == name && len(f) == length { if f[0] != gopkg { return "" } if repoMatch := githubRegexp.FindStringSubmatch(f[2]); repoMatch != nil { return repoMatch[1] } } return "" } if repo := match("go-import", 3); repo != "" { return repo, nil } if repo := match("go-source", 4); repo != "" { return repo, nil } } } func findGitHubRepo(gopkg string) (owner string, repo string, _ error) { ownerrepo, err := findGitHubOwnerRepo(gopkg) if err != nil { return "", "", err } parts := strings.Split(ownerrepo, "/") if got, want := len(parts), 2; got != want { return "", "", fmt.Errorf("invalid GitHub repo: %q does not follow owner/repo", repo) } return parts[0], parts[1], nil } func getLicenseForGopkg(gopkg string) (string, string, error) { owner, repo, err := findGitHubRepo(gopkg) if err != nil { return "", "", err } rl, _, err := gitHub.Repositories.License(context.TODO(), owner, repo) if err != nil { return "", "", err } if deblicense, ok := githubLicenseToDebianLicense[rl.GetLicense().GetKey()]; ok { fulltext := debianLicenseText[deblicense] if fulltext == "" { fulltext = " TODO" } return deblicense, fulltext, nil } return "TODO", " TODO", nil } func getAuthorAndCopyrightForGopkg(gopkg string) (string, string, error) { owner, repo, err := findGitHubRepo(gopkg) if err != nil { return "", "", err } rr, _, err := gitHub.Repositories.Get(context.TODO(), owner, repo) if err != nil { return "", "", err } if strings.TrimSpace(rr.GetOwner().GetURL()) == "" { return "", "", fmt.Errorf("Repository owner URL not present in API response") } ur, _, err := gitHub.Users.Get(context.TODO(), rr.GetOwner().GetLogin()) if err != nil { return "", "", err } copyright := rr.CreatedAt.Format("2006") + " " + ur.GetName() if strings.HasPrefix(repo, "google/") { // As per https://opensource.google.com/docs/creating/, Google retains // the copyright for repositories underneath github.com/google/. copyright = rr.CreatedAt.Format("2006") + " Google Inc." } return ur.GetName(), copyright, nil } // getDescriptionForGopkg gets the package description from GitHub, // intended for the synopsis or the short description in debian/control. func getDescriptionForGopkg(gopkg string) (string, error) { owner, repo, err := findGitHubRepo(gopkg) if err != nil { return "", err } rr, _, err := gitHub.Repositories.Get(context.TODO(), owner, repo) if err != nil { return "", err } return strings.TrimSpace(rr.GetDescription()), nil } func getHomepageForGopkg(gopkg string) string { owner, repo, err := findGitHubRepo(gopkg) if err != nil { return "TODO" } return "https://github.com/" + owner + "/" + repo } dh-make-golang-0.3.3/progress.go000066400000000000000000000022601362560521000164630ustar00rootroot00000000000000package main import ( "fmt" "github.com/mattn/go-isatty" "os" "path/filepath" "strings" "time" ) const ( _ = 1 << (10 * iota) Kibi Mebi Gibi Tebi ) func humanizeBytes(b int64) string { if b > Tebi { return fmt.Sprintf("%.2f TiB", float64(b)/float64(Tebi)) } else if b > Gibi { return fmt.Sprintf("%.2f GiB", float64(b)/float64(Gibi)) } else if b > Mebi { return fmt.Sprintf("%.2f MiB", float64(b)/float64(Mebi)) } else { return fmt.Sprintf("%.2f KiB", float64(b)/float64(Kibi)) } } func progressSize(prefix, path string, done chan struct{}) { // previous holds how many bytes the previous line contained // so that we can clear it in its entirety. var previous int tty := isatty.IsTerminal(os.Stdout.Fd()) for { if tty { var usage int64 filepath.Walk(path, func(path string, info os.FileInfo, err error) error { if err == nil && info.Mode().IsRegular() { usage += info.Size() } return nil }) fmt.Printf("\r%s", strings.Repeat(" ", previous)) previous, _ = fmt.Printf("\r%s: %s", prefix, humanizeBytes(usage)) } select { case <-done: fmt.Printf("\r") return case <-time.After(250 * time.Millisecond): break } } } dh-make-golang-0.3.3/search.go000066400000000000000000000036351362560521000160730ustar00rootroot00000000000000package main import ( "encoding/json" "flag" "fmt" "log" "net/http" "os" "regexp" "strings" ) const ( golangBinariesURL = "https://api.ftp-master.debian.org/binary/by_metadata/Go-Import-Path" ) func getGolangBinaries() (map[string]string, error) { golangBinaries := make(map[string]string) resp, err := http.Get(golangBinariesURL) if err != nil { return nil, fmt.Errorf("getting %q: %v", golangBinariesURL, err) } if got, want := resp.StatusCode, http.StatusOK; got != want { return nil, fmt.Errorf("unexpected HTTP status code: got %d, want %d", got, want) } var pkgs []struct { Binary string `json:"binary"` XSGoImportPath string `json:"metadata_value"` Source string `json:"source"` } if err := json.NewDecoder(resp.Body).Decode(&pkgs); err != nil { return nil, err } for _, pkg := range pkgs { if !strings.HasSuffix(pkg.Binary, "-dev") { continue // skip -dbgsym packages etc. } for _, importPath := range strings.Split(pkg.XSGoImportPath, ",") { // XS-Go-Import-Path can be comma-separated and contain spaces. golangBinaries[strings.TrimSpace(importPath)] = pkg.Binary } } return golangBinaries, nil } func execSearch(args []string) { fs := flag.NewFlagSet("search", flag.ExitOnError) fs.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s search \n", os.Args[0]) fmt.Fprintf(os.Stderr, "Uses Go's default regexp syntax (https://golang.org/pkg/regexp/syntax/)\n") fmt.Fprintf(os.Stderr, "Example: %s search 'debi.*'\n", os.Args[0]) } err := fs.Parse(args) if err != nil { log.Fatal(err) } if fs.NArg() != 1 { fs.Usage() os.Exit(1) } pattern, err := regexp.Compile(fs.Arg(0)) if err != nil { log.Fatal(err) } golangBinaries, err := getGolangBinaries() if err != nil { log.Fatal(err) } for importPath, binary := range golangBinaries { if pattern.MatchString(importPath) { fmt.Printf("%s: %s\n", binary, importPath) } } } dh-make-golang-0.3.3/template.go000066400000000000000000000311541362560521000164360ustar00rootroot00000000000000package main import ( "fmt" "log" "os" "path/filepath" "sort" "strings" "time" ) func writeTemplates(dir, gopkg, debsrc, debLib, debProg, debversion string, pkgType packageType, dependencies []string, u *upstream, dep14, pristineTar bool, ) error { if err := os.Mkdir(filepath.Join(dir, "debian"), 0755); err != nil { return err } if err := os.Mkdir(filepath.Join(dir, "debian", "source"), 0755); err != nil { return err } if err := writeDebianChangelog(dir, debsrc, debversion); err != nil { return err } if err := writeDebianControl(dir, gopkg, debsrc, debLib, debProg, pkgType, dependencies); err != nil { return err } if err := writeDebianCopyright(dir, gopkg, u.vendorDirs, u.hasGodeps); err != nil { return err } if err := writeDebianRules(dir, pkgType); err != nil { return err } var repack bool = len(u.vendorDirs) > 0 || u.hasGodeps if err := writeDebianWatch(dir, gopkg, debsrc, u.hasRelease, repack); err != nil { return err } if err := writeDebianSourceFormat(dir); err != nil { return err } if err := writeDebianPackageInstall(dir, debLib, debProg, pkgType); err != nil { return err } if err := writeDebianGbpConf(dir, dep14, pristineTar); err != nil { return err } if err := writeDebianGitLabCI(dir); err != nil { return err } return nil } func writeDebianChangelog(dir, debsrc, debversion string) error { f, err := os.Create(filepath.Join(dir, "debian", "changelog")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "%s (%s) UNRELEASED; urgency=medium\n", debsrc, debversion) fmt.Fprintf(f, "\n") fmt.Fprintf(f, " * Initial release (Closes: TODO)\n") fmt.Fprintf(f, "\n") fmt.Fprintf(f, " -- %s <%s> %s\n", getDebianName(), getDebianEmail(), time.Now().Format("Mon, 02 Jan 2006 15:04:05 -0700")) return nil } func fprintfControlField(f *os.File, field string, valueArray []string) { switch wrapAndSort { case "a": // Current default, also what "cme fix dpkg" generates fmt.Fprintf(f, "%s: %s\n", field, strings.Join(valueArray, ",\n"+strings.Repeat(" ", len(field)+2))) case "at": // -t, --trailing-comma, preferred by Martina Ferrari // and currently used in quite a few packages fmt.Fprintf(f, "%s: %s,\n", field, strings.Join(valueArray, ",\n"+strings.Repeat(" ", len(field)+2))) case "ast": // -s, --short-indent too, proposed by Guillem Jover fmt.Fprintf(f, "%s:\n %s,\n", field, strings.Join(valueArray, ",\n ")) default: log.Fatalf("%q is not a valid value for -wrap-and-sort, aborting.", wrapAndSort) } } func addDescription(f *os.File, gopkg, comment string) { description, err := getDescriptionForGopkg(gopkg) if err != nil { log.Printf("Could not determine description for %q: %v\n", gopkg, err) description = "TODO: short description" } fmt.Fprintf(f, "Description: %s %s\n", description, comment) longdescription, err := getLongDescriptionForGopkg(gopkg) if err != nil { log.Printf("Could not determine long description for %q: %v\n", gopkg, err) longdescription = "TODO: long description" } fmt.Fprintf(f, " %s\n", longdescription) } func addLibraryPackage(f *os.File, gopkg, debLib string, dependencies []string) { fmt.Fprintf(f, "\n") fmt.Fprintf(f, "Package: %s\n", debLib) fmt.Fprintf(f, "Architecture: all\n") deps := dependencies sort.Strings(deps) deps = append(deps, "${misc:Depends}") fprintfControlField(f, "Depends", deps) addDescription(f, gopkg, "(library)") } func addProgramPackage(f *os.File, gopkg, debProg string) { fmt.Fprintf(f, "\n") fmt.Fprintf(f, "Package: %s\n", debProg) fmt.Fprintf(f, "Architecture: any\n") deps := []string{"${misc:Depends}", "${shlibs:Depends}"} fprintfControlField(f, "Depends", deps) fmt.Fprintf(f, "Built-Using: ${misc:Built-Using}\n") addDescription(f, gopkg, "(program)") } func writeDebianControl(dir, gopkg, debsrc, debLib, debProg string, pkgType packageType, dependencies []string) error { f, err := os.Create(filepath.Join(dir, "debian", "control")) if err != nil { return err } defer f.Close() // Source package: fmt.Fprintf(f, "Source: %s\n", debsrc) fmt.Fprintf(f, "Maintainer: Debian Go Packaging Team \n") fprintfControlField(f, "Uploaders", []string{getDebianName() + " <" + getDebianEmail() + ">"}) // TODO: change this once we have a “golang” section. fmt.Fprintf(f, "Section: devel\n") fmt.Fprintf(f, "Testsuite: autopkgtest-pkg-go\n") fmt.Fprintf(f, "Priority: optional\n") builddeps := append([]string{ "debhelper-compat (= 12)", "dh-golang", "golang-any"}, dependencies...) sort.Strings(builddeps) fprintfControlField(f, "Build-Depends", builddeps) fmt.Fprintf(f, "Standards-Version: 4.5.0\n") fmt.Fprintf(f, "Vcs-Browser: https://salsa.debian.org/go-team/packages/%s\n", debsrc) fmt.Fprintf(f, "Vcs-Git: https://salsa.debian.org/go-team/packages/%s.git\n", debsrc) fmt.Fprintf(f, "Homepage: %s\n", getHomepageForGopkg(gopkg)) fmt.Fprintf(f, "Rules-Requires-Root: no\n") fmt.Fprintf(f, "XS-Go-Import-Path: %s\n", gopkg) // Binary package(s): switch pkgType { case typeLibrary: addLibraryPackage(f, gopkg, debLib, dependencies) case typeProgram: addProgramPackage(f, gopkg, debProg) case typeLibraryProgram: addLibraryPackage(f, gopkg, debLib, dependencies) addProgramPackage(f, gopkg, debProg) case typeProgramLibrary: addProgramPackage(f, gopkg, debProg) addLibraryPackage(f, gopkg, debLib, dependencies) default: log.Fatalf("Invalid pkgType %d in writeDebianControl(), aborting", pkgType) } return nil } func writeDebianCopyright(dir, gopkg string, vendorDirs []string, hasGodeps bool) error { license, fulltext, err := getLicenseForGopkg(gopkg) if err != nil { log.Printf("Could not determine license for %q: %v\n", gopkg, err) license = "TODO" fulltext = "TODO" } f, err := os.Create(filepath.Join(dir, "debian", "copyright")) if err != nil { return err } defer f.Close() _, copyright, err := getAuthorAndCopyrightForGopkg(gopkg) if err != nil { log.Printf("Could not determine copyright for %q: %v\n", gopkg, err) copyright = "TODO" } var indent = " " var linebreak = "" if wrapAndSort == "ast" { indent = " " linebreak = "\n" } fmt.Fprintf(f, "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n") fmt.Fprintf(f, "Upstream-Name: %s\n", filepath.Base(gopkg)) fmt.Fprintf(f, "Upstream-Contact: TODO\n") fmt.Fprintf(f, "Source: %s\n", getHomepageForGopkg(gopkg)) if len(vendorDirs) > 0 || hasGodeps { fmt.Fprintf(f, "Files-Excluded:\n") for _, dir := range vendorDirs { fmt.Fprintf(f, indent+"%s\n", dir) } if hasGodeps { fmt.Fprintf(f, indent+"Godeps/_workspace\n") } } fmt.Fprintf(f, "\n") fmt.Fprintf(f, "Files:"+linebreak+" *\n") fmt.Fprintf(f, "Copyright:"+linebreak+" %s\n", copyright) fmt.Fprintf(f, "License: %s\n", license) fmt.Fprintf(f, "\n") fmt.Fprintf(f, "Files:"+linebreak+" debian/*\n") fmt.Fprintf(f, "Copyright:"+linebreak+" %s %s <%s>\n", time.Now().Format("2006"), getDebianName(), getDebianEmail()) fmt.Fprintf(f, "License: %s\n", license) fmt.Fprintf(f, "Comment: Debian packaging is licensed under the same terms as upstream\n") fmt.Fprintf(f, "\n") fmt.Fprintf(f, "License: %s\n", license) fmt.Fprintf(f, fulltext) return nil } func writeDebianRules(dir string, pkgType packageType) error { f, err := os.Create(filepath.Join(dir, "debian", "rules")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "#!/usr/bin/make -f\n") fmt.Fprintf(f, "\n") fmt.Fprintf(f, "%%:\n") fmt.Fprintf(f, "\tdh $@ --builddirectory=_build --buildsystem=golang --with=golang\n") if pkgType == typeProgram { fmt.Fprintf(f, "\n") fmt.Fprintf(f, "override_dh_auto_install:\n") fmt.Fprintf(f, "\tdh_auto_install -- --no-source\n") } if err := os.Chmod(filepath.Join(dir, "debian", "rules"), 0755); err != nil { return err } return nil } func writeDebianSourceFormat(dir string) error { f, err := os.Create(filepath.Join(dir, "debian", "source", "format")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "3.0 (quilt)\n") return nil } func writeDebianGbpConf(dir string, dep14, pristineTar bool) error { if !(dep14 || pristineTar) { return nil } f, err := os.Create(filepath.Join(dir, "debian", "gbp.conf")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "[DEFAULT]\n") if dep14 { fmt.Fprintf(f, "debian-branch = debian/sid\n") fmt.Fprintf(f, "dist = DEP14\n") } if pristineTar { fmt.Fprintf(f, "pristine-tar = True\n") } return nil } func writeDebianWatch(dir, gopkg, debsrc string, hasRelease bool, repack bool) error { // TODO: Support other hosters too host := "github.com" owner, repo, err := findGitHubRepo(gopkg) if err != nil { log.Printf("debian/watch: Unable to resolve %s to github.com, skipping\n", gopkg) return nil } if !strings.HasPrefix(gopkg, "github.com/") { log.Printf("debian/watch: %s resolves to %s/%s/%s\n", gopkg, host, owner, repo) } f, err := os.Create(filepath.Join(dir, "debian", "watch")) if err != nil { return err } defer f.Close() filenamemanglePattern := `s%%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%%%s-$1.tar.gz%%` uversionmanglePattern := `s/(\d)[_\.\-\+]?(RC|rc|pre|dev|beta|alpha)[.]?(\d*)$/\$1~\$2\$3/` if hasRelease { log.Printf("Setting debian/watch to track release tarball") fmt.Fprintf(f, "version=4\n") fmt.Fprintf(f, `opts="filenamemangle=`+filenamemanglePattern+`,\`+"\n", debsrc) fmt.Fprintf(f, ` uversionmangle=`+uversionmanglePattern) if repack { fmt.Fprintf(f, `,\`+"\n") fmt.Fprintf(f, ` dversionmangle=s/\+ds\d*$//,repacksuffix=+ds1`) } fmt.Fprintf(f, `" \`+"\n") fmt.Fprintf(f, ` https://%s/%s/%s/tags .*/v?(\d\S*)\.tar\.gz debian`+"\n", host, owner, repo) } else { log.Printf("Setting debian/watch to track git HEAD") fmt.Fprintf(f, "version=4\n") fmt.Fprintf(f, `opts="mode=git, pgpmode=none`) if repack { fmt.Fprintf(f, `,\`+"\n") fmt.Fprintf(f, ` dversionmangle=s/\+ds\d*$//,repacksuffix=+ds1`) } fmt.Fprintf(f, `" \`+"\n") fmt.Fprintf(f, ` https://%s/%s/%s.git \`+"\n", host, owner, repo) fmt.Fprintf(f, " HEAD debian\n") // Anticipate that upstream would eventually switch to tagged releases fmt.Fprintf(f, "\n") fmt.Fprintf(f, "# Use the following when upstream starts to tag releases:\n") fmt.Fprintf(f, "#\n") fmt.Fprintf(f, "#version=4\n") fmt.Fprintf(f, `#opts="filenamemangle=`+filenamemanglePattern+`,\`+"\n", debsrc) fmt.Fprintf(f, `# uversionmangle=`+uversionmanglePattern) if repack { fmt.Fprintf(f, `,\`+"\n") fmt.Fprintf(f, `# dversionmangle=s/\+ds\d*$//,repacksuffix=+ds1`) } fmt.Fprintf(f, `" \`+"\n") fmt.Fprintf(f, `# https://%s/%s/%s/tags .*/v?(\d\S*)\.tar\.gz debian`+"\n", host, owner, repo) } return nil } func writeDebianPackageInstall(dir, debLib, debProg string, pkgType packageType) error { if pkgType == typeLibraryProgram || pkgType == typeProgramLibrary { f, err := os.Create(filepath.Join(dir, "debian", debProg+".install")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "usr/bin\n") f, err = os.Create(filepath.Join(dir, "debian", debLib+".install")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "usr/share\n") } return nil } func writeDebianGitLabCI(dir string) error { const gitlabciymlTmpl = `# auto-generated, DO NOT MODIFY. # The authoritative copy of this file lives at: # https://salsa.debian.org/go-team/ci/blob/master/config/gitlabciyml.go image: stapelberg/ci2 test_the_archive: artifacts: paths: - before-applying-commit.json - after-applying-commit.json script: # Create an overlay to discard writes to /srv/gopath/src after the build: - "rm -rf /cache/overlay/{upper,work}" - "mkdir -p /cache/overlay/{upper,work}" - "mount -t overlay overlay -o lowerdir=/srv/gopath/src,upperdir=/cache/overlay/upper,workdir=/cache/overlay/work /srv/gopath/src" - "export GOPATH=/srv/gopath" - "export GOCACHE=/cache/go" # Build the world as-is: - "ci-build -exemptions=/var/lib/ci-build/exemptions.json > before-applying-commit.json" # Copy this package into the overlay: - "GBP_CONF_FILES=:debian/gbp.conf gbp buildpackage --git-no-pristine-tar --git-ignore-branch --git-ignore-new --git-export-dir=/tmp/export --git-no-overlay --git-tarball-dir=/nonexistant --git-cleaner=/bin/true --git-builder='dpkg-buildpackage -S -d --no-sign'" - "pgt-gopath -dsc /tmp/export/*.dsc" # Rebuild the world: - "ci-build -exemptions=/var/lib/ci-build/exemptions.json > after-applying-commit.json" - "ci-diff before-applying-commit.json after-applying-commit.json" ` f, err := os.Create(filepath.Join(dir, "debian", "gitlab-ci.yml")) if err != nil { return err } defer f.Close() fmt.Fprintf(f, gitlabciymlTmpl) return nil } dh-make-golang-0.3.3/version.go000066400000000000000000000100101362560521000162740ustar00rootroot00000000000000package main import ( "fmt" "log" "os" "os/exec" "regexp" "strconv" "strings" "time" ) var ( // describeRegexp parses the count and revision part of the “git describe --long” output. describeRegexp = regexp.MustCompile(`-\d+-g([0-9a-f]+)\s*$`) // semverRegexp checks if a string is a valid Go semver, // from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string // with leading "v" added. semverRegexp = regexp.MustCompile(`^v(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) ) // pkgVersionFromGit determines the actual version to be packaged // from the git repository status and user preference. // Besides returning the Debian upstream version, the "upstream" struct // struct fields u.version, u.commitIsh, u.hasRelease and u.isRelease // are also set. // TODO: also support other VCS func pkgVersionFromGit(gitdir string, u *upstream, forcePrerelease bool) (string, error) { var latestTag string var commitsAhead int // Find @latest version tag (whether annotated or not) cmd := exec.Command("git", "describe", "--abbrev=0", "--tags", "--exclude", "*/v*") cmd.Dir = gitdir if out, err := cmd.Output(); err == nil { latestTag = strings.TrimSpace(string(out)) u.hasRelease = true log.Printf("Found latest tag %q", latestTag) if !semverRegexp.MatchString(latestTag) { log.Printf("WARNING: Latest tag %q is not a valid SemVer version\n", latestTag) // TODO: Enforce strict sementic versioning with leading "v"? } // Count number of commits since @latest version cmd = exec.Command("git", "rev-list", "--count", latestTag+"..HEAD") cmd.Dir = gitdir out, err := cmd.Output() if err != nil { return "", err } commitsAhead, err = strconv.Atoi(strings.TrimSpace(string(out))) if err != nil { return "", err } if commitsAhead == 0 { // Equivalent to "git describe --exact-match --tags" log.Printf("Latest tag %q matches master", latestTag) } else { log.Printf("INFO: master is ahead of %q by %v commits", latestTag, commitsAhead) } u.commitIsh = latestTag u.version = strings.TrimPrefix(latestTag, "v") if forcePrerelease { log.Printf("INFO: Force packaging master (prerelease) as requested by user") // Fallthrough to package @master (prerelease) } else { u.isRelease = true return u.version, nil } } // Packaging @master (prerelease) // 1.0~rc1 < 1.0 < 1.0+b1, as per // https://www.debian.org/doc/manuals/maint-guide/first.en.html#namever mainVer := "0.0~" if u.hasRelease { mainVer = u.version + "+" } // Find committer date, UNIX timestamp cmd = exec.Command("git", "log", "--pretty=format:%ct", "-n1") cmd.Dir = gitdir lastCommitUnixBytes, err := cmd.Output() if err != nil { return "", err } lastCommitUnix, err := strconv.ParseInt(strings.TrimSpace(string(lastCommitUnixBytes)), 0, 64) if err != nil { return "", err } // This results in an output like "v4.10.2-232-g9f107c8" cmd = exec.Command("git", "describe", "--long", "--tags") cmd.Dir = gitdir lastCommitHash := "" describeBytes, err := cmd.Output() if err != nil { // In case there are no tags at all, we just use the sha of the current commit cmd = exec.Command("git", "rev-parse", "--short", "HEAD") cmd.Dir = gitdir cmd.Stderr = os.Stderr revparseBytes, err := cmd.Output() if err != nil { return "", err } lastCommitHash = strings.TrimSpace(string(revparseBytes)) u.commitIsh = lastCommitHash } else { submatches := describeRegexp.FindSubmatch(describeBytes) if submatches == nil { return "", fmt.Errorf("git describe output %q does not match expected format", string(describeBytes)) } lastCommitHash = string(submatches[1]) u.commitIsh = strings.TrimSpace(string(describeBytes)) } u.version = fmt.Sprintf("%sgit%s.%s", mainVer, time.Unix(lastCommitUnix, 0).UTC().Format("20060102"), lastCommitHash) return u.version, nil } dh-make-golang-0.3.3/version_current.go000066400000000000000000000011131362560521000200420ustar00rootroot00000000000000package main import ( "fmt" "runtime" ) // Version represents the dh-make-golang build version. type Version struct { major int minor int patch int preRelease string } var currentVersion = Version{ major: 0, minor: 3, patch: 3, preRelease: "", } func (v Version) String() string { return fmt.Sprintf("%d.%d.%d%s", v.major, v.minor, v.patch, v.preRelease) } func buildVersionString() string { version := "v" + currentVersion.String() osArch := runtime.GOOS + "/" + runtime.GOARCH return fmt.Sprintf("%s %s %s", program, version, osArch) } dh-make-golang-0.3.3/version_test.go000066400000000000000000000057731362560521000173570ustar00rootroot00000000000000package main import ( "io/ioutil" "os" "os/exec" "path/filepath" "strings" "testing" ) func gitCmdOrFatal(t *testing.T, tempdir string, arg ...string) { cmd := exec.Command("git", arg...) cmd.Dir = tempdir cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { t.Fatalf("Could not run %v: %v", cmd.Args, err) } } func TestSnapshotVersion(t *testing.T) { tempdir, err := ioutil.TempDir("", "dh-make-golang") if err != nil { t.Fatalf("Could not create temp dir: %v", err) } defer os.RemoveAll(tempdir) tempfile := filepath.Join(tempdir, "test") if err := ioutil.WriteFile(tempfile, []byte("testcase"), 0644); err != nil { t.Fatalf("Could not write temp file %q: %v", tempfile, err) } gitCmdOrFatal(t, tempdir, "init") gitCmdOrFatal(t, tempdir, "config", "user.email", "unittest@example.com") gitCmdOrFatal(t, tempdir, "config", "user.name", "Unit Test") gitCmdOrFatal(t, tempdir, "add", "test") cmd := exec.Command("git", "commit", "-a", "-m", "initial commit") cmd.Env = append(os.Environ(), "GIT_COMMITTER_DATE=2015-04-20T11:22:33") cmd.Dir = tempdir cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { t.Fatalf("Could not run %v: %v", cmd.Args, err) } var u upstream got, err := pkgVersionFromGit(tempdir, &u, false) if err != nil { t.Fatalf("Determining package version from git failed: %v", err) } if want := "0.0~git20150420."; !strings.HasPrefix(got, want) { t.Errorf("got %q, want %q", got, want) } gitCmdOrFatal(t, tempdir, "tag", "-a", "v1", "-m", "release v1") got, err = pkgVersionFromGit(tempdir, &u, false) if err != nil { t.Fatalf("Determining package version from git failed: %v", err) } if want := "1"; got != want { t.Logf("got %q, want %q", got, want) } if err := ioutil.WriteFile(tempfile, []byte("testcase 2"), 0644); err != nil { t.Fatalf("Could not write temp file %q: %v", tempfile, err) } cmd = exec.Command("git", "commit", "-a", "-m", "first change") cmd.Env = append(os.Environ(), "GIT_COMMITTER_DATE=2015-05-07T11:22:33") cmd.Dir = tempdir cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { t.Fatalf("Could not run %v: %v", cmd.Args, err) } got, err = pkgVersionFromGit(tempdir, &u, false) if err != nil { t.Fatalf("Determining package version from git failed: %v", err) } if want := "1+git20150507.1."; !strings.HasPrefix(got, want) { t.Logf("got %q, want %q", got, want) } if err := ioutil.WriteFile(tempfile, []byte("testcase 3"), 0644); err != nil { t.Fatalf("Could not write temp file %q: %v", tempfile, err) } cmd = exec.Command("git", "commit", "-a", "-m", "second change") cmd.Env = append(os.Environ(), "GIT_COMMITTER_DATE=2015-05-08T11:22:33") cmd.Dir = tempdir cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { t.Fatalf("Could not run %v: %v", cmd.Args, err) } got, err = pkgVersionFromGit(tempdir, &u, false) if err != nil { t.Fatalf("Determining package version from git failed: %v", err) } if want := "1+git20150508.2."; !strings.HasPrefix(got, want) { t.Logf("got %q, want %q", got, want) } }