-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
1,388 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
// cmd/pkg-config is a Go-centric and GOPATH-aware pkg-config replacement | ||
// for use with the cgo tool. | ||
// | ||
// Using cmd/pkg-config with the cgo command | ||
// | ||
// To use cmd/pkg-config go install it and ensure it's in the PATH. A NOTE for | ||
// Linux users: the cmd/pkg-config must be present before original | ||
// /usr/bin/pkg-config in the PATH list; it is not advised to replace it, | ||
// as cmd/pkg-config is not a full replacement for the pkg-config tool, e.g. | ||
// it does not implement conflict and dependency resolving. | ||
// | ||
// The cgo tool uses pkg-config for obtaining CFLAGS and LDFLAGS of C libraries. | ||
// Example: | ||
// | ||
// // #cgo pkg-config: libpng | ||
// // #include <png.h> | ||
// import "C" | ||
// | ||
// The "#cgo pkg-config: png" directive makes a cgo tool query a pkg-config for | ||
// CFLAGS during generation of a C wrapper (--cflags flag) and for LDFLAGS during | ||
// linking (--libs flag). The original pkg-config looks up a libpng.pc file in | ||
// a directory list specified by the PKG_CONFIG_PATH environment variable plus | ||
// a few other default ones. The libpng.pc file can have the following content: | ||
// | ||
// prefix=/usr | ||
// exec_prefix=${prefix} | ||
// libdir=${prefix}/lib/x86_64-linux-gnu | ||
// includedir=${prefix}/include/libpng12 | ||
// | ||
// Name: libpng | ||
// Description: Loads and saves PNG files | ||
// Version: 1.2.46 | ||
// Libs: -L${libdir} -lpng12 | ||
// Libs.private: -lz -lm | ||
// Cflags: -I${includedir} | ||
// | ||
// A .pc file is composed of two parts - variables and keywords. Keywords may | ||
// reference pre-declared variables. | ||
// | ||
// $ pkg-config --cflags libpng | ||
// -I/usr/include/libpng12 | ||
// $ pkg-config --libs libpng | ||
// -L/usr/lib/x86_64-linux-gnu -lpng12 | ||
// $ pkg-config --cflags --libs libpng | ||
// -I/usr/include/libpng12 -L/usr/lib/x86_64-linux-gnu -lpng12 | ||
// | ||
// The cmd/pkg-config tool looks up for a PC file in two other places in addition | ||
// do the original pkg-config: $GOPATH and github.com. | ||
// | ||
// The cmd/pkg-config tool and $GOPATH | ||
// | ||
// The cmd/pkg-config defines standard directory layout for C libraries: | ||
// | ||
// - $GOPATH/lib contains both the .pc files and binaries | ||
// - $GOPATH/include contains package headers | ||
// | ||
// The $GOPATH tree for the example may look like the following: | ||
// | ||
// $GOPATH | ||
// ├── include | ||
// │ └── libpng | ||
// │ └── libpng12 | ||
// │ ├── pngconf.h | ||
// │ └── png.h | ||
// ├── lib | ||
// │ ├── linux_amd64 | ||
// │ │ └── libpng | ||
// │ │ ├── libpng12.so | ||
// │ │ └── libpng.pc | ||
// │ └── windows_amd64 | ||
// │ └── libpng | ||
// │ ├── libpng12.dll | ||
// │ ├── libpng12.dll.a | ||
// │ └── libpng.pc | ||
// └── src | ||
// └── github.com | ||
// └── joe | ||
// └── png-wrapper | ||
// └── png-wrapper.go | ||
// | ||
// The cmd/pkg-config reads the libpng.pc file from $GOPATH/lib/libpng/$GOOS_$GOARCH/libpng.pc. | ||
// The .pc file written for cmd/pkg-config can use $GOPATH, $GOOS and $GOARCH | ||
// builtin variables, which are expanded by the cmd/pkg-config during runtime. | ||
// The rewritten .pc file for libpng may look like the following: | ||
// | ||
// libdir=${GOPATH}/lib/${GOOS}_${GOARCH}/libpng | ||
// includedir=${GOPATH}/include/libpng | ||
// | ||
// Name: libpng | ||
// Description: Loads and saves PNG files | ||
// Version: 1.2.46 | ||
// Libs: -L${libdir} -lpng12 | ||
// Libs.private: -lz -lm | ||
// Cflags: -I${includedir} | ||
// | ||
// The cmd/pkg-config tool and github.com | ||
// | ||
// Although it's advised to always use an official or self-compiled libraries for | ||
// a production use, cmd/pkg-config can download a zip archive from project's | ||
// github.com releases for a pkg-config tag and unpack it into $GOPATH. | ||
// For example in order to make the above github.com/joe/png-wrapper package | ||
// pkg-config-gettable, it's enough to zip include/ and lib/ directories: | ||
// | ||
// $ zip -9 -r libpng.zip include dir | ||
// | ||
// Create release, name a tag after pkg-config and attach libpng.zip do the | ||
// file list. This would make the libpng.zip archive be accessible from the following | ||
// link: | ||
// | ||
// $ wget http://github.com/joe/png-wrapper/releases/download/pkg-config/libpng.zip | ||
// | ||
// Which is the default location the cmd/pkg-config searches for libraries. Then | ||
// go-getting a joe/png-wrapper package altogether with C dependencies is as | ||
// easy as: | ||
// | ||
// $ go get github.com/joe/png-wrapper | ||
// | ||
// The cmd/pkg-config tool looks up a .pc file for a $LIBRARY in the following order: | ||
// | ||
// - $GOPATH/lib/$GOOS_$GOARCH/$LIBRARY/$LIBRARY.pc | ||
// - http://github.com/$USER/$PROJECT/releases/download/pkg-config/$LIBRARY.zip | ||
// - $PKG_CONFIG_PATH and eventual pkg-config's default search locations (platform-specific) | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/rjeczalik/pkgconfig" | ||
) | ||
|
||
const USAGE = `NAME: | ||
pkg-config - Go-centric pkg-config replacement | ||
USAGE: | ||
pkg-config --libs PKG | ||
pkg-config --cflags PKG | ||
pkg-config --cflags --libs PKG1 PKG2 | ||
pkg-config get github.com/USER/PROJ PKG` | ||
|
||
func die(v ...interface{}) { | ||
for _, v := range v { | ||
fmt.Fprintln(os.Stderr, v) | ||
} | ||
os.Exit(1) | ||
} | ||
|
||
func ishelp(s string) bool { | ||
return s == "-h" || s == "-help" || s == "help" || s == "--help" || s == "/?" | ||
} | ||
|
||
func main() { | ||
if len(os.Args) == 1 || (len(os.Args) == 2 && ishelp(os.Args[1])) { | ||
fmt.Println(USAGE) | ||
} else { | ||
switch os.Args[1] { | ||
case "get": | ||
if len(os.Args) != 4 { | ||
die(USAGE) | ||
} | ||
pc, err := pkgconfig.LookupGithubProj(os.Args[3], os.Args[2]) | ||
if err != nil { | ||
die(err) | ||
} | ||
_ = pc | ||
default: | ||
pkg := pkgconfig.NewPkgArgs(os.Args[1:]) | ||
if err := pkg.Resolve(); err == nil { | ||
pkg.WriteTo(os.Stdout) | ||
} else { | ||
die(err) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
package pkgconfig | ||
|
||
import ( | ||
"archive/zip" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"net/http" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
var githubProj string | ||
|
||
func init() { | ||
if wd, err := os.Getwd(); err == nil { | ||
githubProj = extractproj(wd, os.PathSeparator) | ||
} | ||
} | ||
|
||
var src = map[rune]string{'/': "/src/", '\\': `\src\`} | ||
|
||
var projpath = map[rune]func(string) string{ | ||
'/': func(s string) string { return s }, | ||
'\\': func(s string) string { return strings.Replace(s, "\\", "/", -1) }, | ||
} | ||
|
||
func extractproj(path string, sep rune) (proj string) { | ||
n := strings.LastIndex(path, src[sep]) | ||
if n == -1 { | ||
return | ||
} | ||
n += len(src[sep]) | ||
m := n | ||
for i := 0; i < 3; i++ { | ||
l := strings.Index(path[m:], string(sep)) | ||
if l == -1 { | ||
m = len(path) + 1 | ||
break | ||
} | ||
m += l + 1 | ||
} | ||
proj = projpath[sep](string(path[n : m-1])) | ||
if !strings.HasPrefix(proj, "github.com") { | ||
proj = "" | ||
} | ||
return | ||
} | ||
|
||
// LookupGithub TODO(rjeczalik): document | ||
func LookupGithub(pkg string) (*PC, error) { | ||
if githubProj == "" { | ||
return nil, errors.New(`unable to guess project's URL from $CWD`) | ||
} | ||
return LookupGithubProj(pkg, githubProj) | ||
} | ||
|
||
var targets = map[string]struct{}{ | ||
"darwin_386": {}, | ||
"darwin_amd64": {}, | ||
"freebsd_386": {}, | ||
"freebsd_amd64": {}, | ||
"freebsd_arm": {}, | ||
"linux_386": {}, | ||
"linux_amd64": {}, | ||
"linux_arm": {}, | ||
"netbsd_386": {}, | ||
"netbsd_amd64": {}, | ||
"netbsd_arm": {}, | ||
"openbsd_386": {}, | ||
"openbsd_amd64": {}, | ||
"plan9_386": {}, | ||
"plan9_amd64": {}, | ||
"windows_386": {}, | ||
"windows_amd64": {}, | ||
} | ||
|
||
const targetsMinKeyLen = 9 | ||
|
||
func validFile(path, pkg string) bool { | ||
switch { | ||
case strings.HasPrefix(path, "include/"): | ||
i := len("include/") | ||
if len(path) < i+len(pkg)+1 { | ||
return false | ||
} | ||
n := strings.Index(path[i:], "/") | ||
if n == -1 { | ||
return false | ||
} | ||
return string(path[i:i+n]) == pkg | ||
case strings.HasPrefix(path, "lib/"): | ||
i := len("lib/") | ||
if len(path) < i+targetsMinKeyLen+1+len(pkg)+1 { | ||
return false | ||
} | ||
n := strings.Index(path[i:], "/") | ||
if n == -1 { | ||
return false | ||
} | ||
if _, ok := targets[string(path[i:i+n])]; !ok { | ||
return false | ||
} | ||
i += n + 1 | ||
if n = strings.Index(path[i:], "/"); n == -1 { | ||
return false | ||
} | ||
return string(path[i:i+n]) == pkg | ||
} | ||
return false | ||
} | ||
|
||
var replacefile = map[rune]func(string) string{ | ||
'/': func(s string) string { return s }, | ||
'\\': func(s string) string { return strings.Replace(s, "/", "\\", -1) }, | ||
} | ||
|
||
func copyFile(gopath string, f *zip.File) (err error) { | ||
rc, err := f.Open() | ||
if err != nil { | ||
return | ||
} | ||
defer rc.Close() | ||
dir := filepath.Join(gopath, replacefile[os.PathSeparator](path.Dir(f.Name))) | ||
if err = os.MkdirAll(dir, 0755); err != nil { | ||
return | ||
} | ||
file, err := os.OpenFile(filepath.Join(dir, path.Base(f.Name)), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) | ||
if err != nil { | ||
return | ||
} | ||
_, err = io.Copy(file, rc) | ||
file.Close() | ||
return | ||
} | ||
|
||
// LookupGithunProj TODO(rjeczalik): document | ||
func LookupGithubProj(pkg, proj string) (*PC, error) { | ||
path := os.Getenv("GOPATH") | ||
if p := strings.Split(path, string(os.PathListSeparator)); len(path) != 0 { | ||
path = p[0] | ||
} | ||
if path == "" { | ||
return nil, errors.New("$GOPATH is empty") | ||
} | ||
url := fmt.Sprintf("http://%s/releases/download/pkg-config/%s.zip", proj, pkg) | ||
res, err := http.Get(url) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer res.Body.Close() | ||
if res.StatusCode == http.StatusNotFound { | ||
return nil, errors.New("not found: " + url) | ||
} | ||
f, err := ioutil.TempFile("", pkg) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if _, err = io.Copy(f, res.Body); err != nil { | ||
f.Close() | ||
os.Remove(f.Name()) | ||
return nil, err | ||
} | ||
f.Close() | ||
r, err := zip.OpenReader(f.Name()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer func() { | ||
r.Close() | ||
os.Remove(f.Name()) | ||
}() | ||
for _, f := range r.File { | ||
// Filter out directories. | ||
if f.Name[len(f.Name)-1] != '/' { | ||
if !validFile(f.Name, pkg) { | ||
return nil, fmt.Errorf("unexcpected file %q", f.Name) | ||
} | ||
if err = copyFile(path, f); err != nil { | ||
return nil, err | ||
} | ||
} | ||
} | ||
return LookupGopath(pkg) | ||
} |
Oops, something went wrong.