├── .gitignore ├── .goxc.json ├── .travis.yml ├── README.md ├── archive ├── .gitignore ├── archive.go ├── targz.go └── zip.go ├── config ├── buildsettings.go ├── cli.go ├── cli_test.go ├── defaults.go ├── json.go ├── json_test.go ├── settings.go ├── sliceflags.go ├── test-goxc.json └── testdata │ └── invalid.goxc.json ├── core ├── constants.go ├── core.go └── core_test.go ├── darwin.goxc.json ├── deb-contents └── etc │ └── init.d │ └── dummy ├── debian ├── changelog ├── control ├── copyright └── rules ├── downloads.htpl ├── downloads.tpl ├── executils ├── exec.go └── exec_test.go ├── exefileparse └── parseexe.go ├── goxc.go ├── goxc_test.go ├── htmltemplate.goxc.json ├── notdarwin.goxc.json ├── packaging └── sdeb │ ├── constants.go │ ├── sdeb.go │ └── sdeb_test.go ├── platforms ├── buildconstraints.go ├── buildconstraints_test.go └── platforms.go ├── sample.goxc.local.json ├── source ├── parser.go └── parser_test.go ├── tasks ├── archive.go ├── bintray.go ├── bump.go ├── clean-destination.go ├── codesign.go ├── copy-resources.go ├── deb-dev.go ├── deb-source.go ├── deb.go ├── downloads-page.go ├── github │ ├── github.go │ ├── github_task.go │ └── github_test.go ├── go-clean.go ├── go-fmt.go ├── go-install.go ├── go-test.go ├── go-vet.go ├── http.go ├── httpc │ └── http.go ├── interpolate-source.go ├── rice-append.go ├── rmbin.go ├── tag.go ├── tasks.go ├── tasks_test.go ├── toolchain.go └── xc.go ├── testdata └── test.go └── typeutils ├── mapstringinterfaceutils.go ├── stringslices.go └── stringslices_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test* 9 | bin 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | 25 | *.swp 26 | 27 | 28 | " goxc 'ignore' files 29 | .goxc-temp 30 | *.local.json 31 | test.json 32 | goxc 33 | 34 | Godeps 35 | public 36 | 37 | DEBIAN 38 | -------------------------------------------------------------------------------- /.goxc.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppName": "goxc", 3 | "ArtifactsDest": "../goxc-pages/dl", 4 | "Tasks": [ 5 | "interpolate-source", 6 | "go-fmt", 7 | "default", 8 | "deb-source" 9 | ], 10 | "ResourcesInclude": "README.md,.\\_test1/*,_test2", 11 | "ResourcesExclude": "*.go", 12 | "PackageVersion": "0.18.1", 13 | "TaskSettings": { 14 | "archive-zip": {}, 15 | "bintray": { 16 | "downloadspage": "bintray.md", 17 | "package": "goxc", 18 | "repository": "utils", 19 | "subject": "laher" 20 | }, 21 | "codesign": { 22 | "id": "goxc" 23 | }, 24 | "debs": { 25 | "metadata": { 26 | "description": "Cross-compiler utility for Go", 27 | "maintainer": "Am Laher (https://github.com/laher)" 28 | }, 29 | "metadata-deb": { 30 | "Depends": "golang", 31 | "Homepage": "https://github.com/laher/goxc" 32 | }, 33 | "other-mapped-files": { 34 | "/": "deb-contents/" 35 | } 36 | }, 37 | "downloads-page": { 38 | "filename": "index.md", 39 | "templateFile": "downloads.tpl" 40 | }, 41 | "go-test": { 42 | "i": true, 43 | "short": true 44 | }, 45 | "publish-github": { 46 | "owner": "laher", 47 | "prerelease": false, 48 | "repository": "goxc" 49 | }, 50 | "xc": { 51 | "test-setting": "test-value" 52 | } 53 | }, 54 | "ConfigVersion": "0.9", 55 | "BuildSettings": { 56 | "LdFlags": "-s", 57 | "LdFlagsXVars": { 58 | "TimeNow": "main.BUILD_DATE", 59 | "Version": "main.VERSION" 60 | } 61 | }, 62 | "Env": [ 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.4.2 5 | - master 6 | os: 7 | - linux 8 | - osx 9 | 10 | script: 11 | - go test -v ./... 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | goxc [![build status](http://img.shields.io/travis/laher/goxc.svg)](https://travis-ci.org/laher/goxc) 2 | ==== 3 | 4 | _NOTE: goxc has long been in maintenance mode. Ever since Go1.5 supported simple cross-compilation, this tool lost much of its value. There are still many aspects of goxc which I'm very proud of, and some packaging features in particular, which are still useful. I'm very much a go user, but I myself haven't had any need for goxc for a long while._ 5 | 6 | _If you see something you'd like to add to goxc, please go ahead, fork and PR. Good PRs will earn you commit access to the repo. Thanks for everyone's help over the years._ 7 | 8 | [goxc](https://github.com/laher/goxc) is a build tool for Go, with a focus on cross-compiling and packaging. 9 | 10 | 11 | By default, goxc [g]zips (& .debs for Linux) the programs, and generates a 'downloads page' in markdown (with a Jekyll header). 12 | 13 | goxc is written in Go but uses *os.exec* to call 'go build' with the appropriate flags & env variables for each supported platform. 14 | 15 | goxc was inspired by Dave Cheney's Bash script [golang-crosscompile](https://github.com/davecheney/golang-crosscompile). 16 | 17 | * goxc crosscompiles to all platforms at once. 18 | * The artifacts are saved into a directory structure along with a markdown file of relative links. 19 | * Artifacts are packaged as zips, tgzs, or debs. Defaults for each OS/architecture. 20 | * AND, goxc can now upload your files to github releases OR bintray.com. 21 | * **See ‘Github Releases’ section below.** 22 | 23 | 24 | Notable Features 25 | ---------------- 26 | * Cross-compilation, to all supported platforms, or a specified subset. 27 | * Validation of toolchain & verification of cross-compiled artifacts 28 | * Specify target platform, via 'Build Constraint'-like syntax (via commandline flag e.g. `-bc="windows linux,!arm"`, or via config) 29 | * *Automatic* (re-)building toolchain to all or specified platforms. 30 | * 'task' based invocation, similar to 'make' or 'ant'. e.g. `goxc xc` or `goxc clean go-test` 31 | * The 'default' task alias will, test, cross-compile, verify, package up your artifacts for each platform, and generate a 'downloads page' with links to each platform. 32 | * Various go tools available via tasks: `go-test`, `go-vet`, `go-fmt`, `go-install`, `go-clean`. 33 | * You can modify task behaviour via configuration or commandline flags. 34 | * JSON-based configuration files for repeatable builds. 35 | * Most config data can be written via flags (using -wc option) - less JSON fiddliness. 36 | * Includes support for multiple configurations per-project. 37 | * Per-task configuration options. 38 | * 'Override' config files for 'local environment' - working-copy-specific, or branch-specific, configurations. 39 | * Packaging & distribution 40 | * Zip (or tar.gz) archiving of cross-compiled artifacts & accompanying resources (READMEs etc) 41 | * Packaging into .debs (for Debian/Ubuntu Linux) 42 | * Upload to github.com releases. 43 | * bintray.com integration (deploys binaries to bintray.com). *bintray.com registration required* 44 | * 'downloads page' generation (markdown/html format; templatable). 45 | * Versioning: 46 | * track your version number via configuration data. 47 | * version number interpolation at compile-time (uses go's `-ldflags` compiler option to populate given constants or global variables with build version or build date) 48 | * version number interpolation of source code. `goxc interpolate-source` (new task available in 0.10.x). 49 | * the `bump` task facilitates increasing the app version number. 50 | * the `tag` task creates a tag in your vcs (currently only 'git' supported). 51 | * support for multiple binaries per project (goxc now searches subdirectories for 'main' packages) 52 | * support for multiple Go installations - choose at runtime with `-goroot=` flag. 53 | 54 | Installation 55 | -------------- 56 | goxc requires the go source and the go toolchain. 57 | 58 | 1. [Install go from source](http://golang.org/doc/install/source). (Requires gcc (or MinGW) and 'hg') 59 | 60 | * OSX Users Note: If you are using XCode 5 (OSX 10.9), it is best to go straight to Go 1.2rc5 (or greater). This is necessary because Apple have replaced the standard gcc with llvm-gcc, and Go 1.1 compilation tools depend on the usual gcc. 61 | 62 | * There is another workaround incase Go 1.2rc5 is not an option: 63 | 64 | brew tap homebrew/versions 65 | brew install apple-gcc42 66 | go get github.com/laher/goxc 67 | CC=`brew list apple-gcc42 | grep bin/gcc-4.2` goxc -t 68 | 69 | 2. Install goxc: 70 | 71 | go get github.com/laher/goxc 72 | 73 | 3. a. (just once per Go version): to pre-build the toolchains for all platforms: 74 | 75 | goxc -t 76 | 77 | 78 | * Note that rebuilding the toolchain is only required for Go up until v1.4. This step will become unnecessary in Go 1.5. 79 | * Note that, until goxc v0.16.0, rebuilding the toolchain was triggered automatically. This has now been switched off (by default). Automatic rebuilding was causing a number of subtle bugs for different users, and has been switched off since v0.16.0. 80 | * Also note that building the toolchain takes a while. Cross-compilation itself is quite fast. 81 | 82 | Basic Usage 83 | ----------- 84 | 85 | cd path/to/app/dir 86 | goxc 87 | 88 | 89 | More options 90 | ------------ 91 | 92 | Use `goxc -h` to list all options. 93 | 94 | * e.g. To restrict by OS and Architecture (using the same syntax as Go Build Constraints): 95 | 96 | goxc -bc="linux,!arm windows,386 darwin" 97 | 98 | * Note that build constraints are described in Go's ['build' package documentation](http://golang.org/pkg/go/build/) (in the overview section). 99 | 100 | * e.g. To set a destination root directory and artifact version number: 101 | 102 | goxc -d=my/jekyll/site/downloads -pv=0.1.1 103 | 104 | * 'Package version' can be compiled into your app if you define a VERSION variable in your main package. 105 | 106 | "Tasks" 107 | ------- 108 | 109 | goxc performs a number of operations, defined as 'tasks'. You can specify tasks as commandline arguments 110 | 111 | * `goxc -t` performs one task called 'toolchain'. It's the equivalent of `goxc -d=~ toolchain` 112 | * The *default* task is actually several tasks, which can be summarised as follows: 113 | * validate (tests the code) -> compile (cross-compiles code) -> package ([g]zips up the executables and builds a 'downloads' page) 114 | * You can specify one or more tasks, such as `goxc go-fmt xc` 115 | * You can skip tasks with '-tasks-='. Skip the 'package' stage with `goxc -tasks-=package` 116 | * For a list of tasks and 'aliases', run `goxc -h tasks` 117 | * Several tasks have options available for overriding. You can specify them in config or via flags. Just use `goxc -task-setting=value ` 118 | * For more info on a particular task, run `goxc -h `. This will also show you the options available for that task. 119 | * The easiest way to see how to configure tasks in config is to write some task config via `-wc`, e.g. `goxc -wc xc -GOARM=5` 120 | 121 | Outcome 122 | ------- 123 | 124 | By default, artifacts are generated and then immediately archived into (outputdir). 125 | 126 | Examples: 127 | 128 | * /my/outputdir/0.1.1/myapp\_0.1.1\_linux\_arm.tar.gz 129 | * /my/outputdir/0.1.1/myapp\_0.1.1\_windows\_386.zip 130 | * /my/outputdir/0.1.1/myapp\_0.1.1\_linux\_386.deb 131 | 132 | The version number is specified with -pv=0.1.1 . 133 | 134 | By default, the output directory is ($GOBIN)/(appname)-xc, and the version is 'unknown', but you can specify these. 135 | 136 | e.g. 137 | 138 | goxc -pv=0.1.1 -d=/home/me/myuser-github-pages/myapp/downloads/ 139 | 140 | *NOTE: it's **bad idea** to use project-level github-pages - your repo will become huge. User-level gh-pages are an option, but it's better to use the 'bintray' tasks.*: 141 | 142 | If non-archived, artifacts generated into a directory structure as follows: 143 | 144 | (outputdir)/(version)/(OS)\_(ARCH)/(appname)(.exe?) 145 | 146 | Be careful if you want to build a project with multiple executables. You need to add `{{.ExeName}}` to your `OutPath`-setting in your '.goxc.json'. So it may look like the following code snippet. 147 | 148 | ``` 149 | "OutPath": "{{.Dest}}{{.PS}}{{.AppName}}{{.PS}}{{.Version}}{{.PS}}{{.ExeName}}_{{.Version}}_{{.Os}}_{{.Arch}}{{.Ext}}" 150 | ``` 151 | 152 | Configuration file 153 | ----------------- 154 | 155 | For repeatable builds (and some extra options), it is recomended to use goxc with one or more configuration file(s) to save and re-run compilations. 156 | 157 | To create a config file (`.goxc.json`), just use the -wc (write config) option. 158 | 159 | goxc -wc -d=../site/downloads -bc="linux windows" xc -GOARM=7 160 | 161 | You can also use multiple config files to support different paremeters for each platform. 162 | 163 | The following would add a 'local' config file, `.goxc.local.json`. This file's contents will override `.goxc.json`. The idea of the .local.json files is to git-ignore them - for any local parameters which you only want on this particular computer, but not for other users or even for yourself on other computers/OS's. 164 | 165 | goxc -wlc -d=../site/downloads 166 | 167 | The configuration file(s) feature is documented in much more detail in [the wiki](https://github.com/laher/goxc/wiki/config) 168 | 169 | 170 | Github Releases 171 | --------------- 172 | 173 | This is the good stuff, so let’s go from the top. 174 | 175 | * *First, install Go from source, and goxc. See ‘Installation’, above* 176 | 177 | * If you haven’t already, build toolchain (all platforms!). This takes a while. 178 | 179 | ``` 180 | goxc -t 181 | ``` 182 | 183 | * Write a config file `.goxc.json` with info about your repo 184 | 185 | ``` 186 | goxc -wc default publish-github -owner= 187 | goxc -wc default publish-github -repository= 188 | cat .goxc.json 189 | ``` 190 | 191 | * Bump a version, to get a meaningful version number. 192 | 193 | ``` 194 | goxc bump 195 | ``` 196 | 197 | * *Go to your github account and create a personal access token* 198 | 199 | [https://github.com/settings/tokens](https://github.com/settings/tokens) 200 | 201 | * Write a local config file `.goxc.local.json` with your key info, ensuring that the key doesn’t end up in your git repo. See more about config files in [the wiki](https://github.com/laher/goxc/wiki/config) 202 | 203 | ``` 204 | goxc -wlc default publish-github -apikey=123456789012 205 | echo ".goxc.local.json" >> .gitignore 206 | ``` 207 | 208 | *Note that you can put a dummy key into the commandline and edit the file later with the real key.* 209 | 210 | * Now, cross-compile, package and upload. All in one go. 211 | 212 | ``` 213 | goxc 214 | ``` 215 | 216 | There’s heaps of ways to reconfigure each task to get the outcome you really want, but this produces some pretty sensible defaults. Have fun. 217 | 218 | Limitations 219 | ----------- 220 | 221 | * Tested on Linux and Mac recently. Windows - some time ago now. 222 | * Currently goxc is only designed to build standalone Go apps without linked libraries. You can try but YMMV 223 | * The *API* is not considered stable yet, so please don't start embedding goxc method calls in your code yet - unless you 'Contact us' first! Then I can freeze some API details as required. 224 | * Bug: issue with config overriding. Empty strings do not currently override non-empty strings. e.g. `-pi=""` doesnt override the associated config setting PackageInfo 225 | 226 | License 227 | ------- 228 | 229 | Copyright 2013 Am Laher 230 | 231 | Licensed under the Apache License, Version 2.0 (the "License"); 232 | you may not use this file except in compliance with the License. 233 | You may obtain a copy of the License at 234 | 235 | http://www.apache.org/licenses/LICENSE-2.0 236 | 237 | Unless required by applicable law or agreed to in writing, software 238 | distributed under the License is distributed on an "AS IS" BASIS, 239 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 240 | See the License for the specific language governing permissions and 241 | limitations under the License. 242 | 243 | See also 244 | -------- 245 | * [Package Versioning](https://github.com/laher/goxc/wiki/versioning) 246 | * [Wiki home](https://github.com/laher/goxc/wiki) 247 | * [Contributions](https://github.com/laher/goxc/wiki/contributions) 248 | -------------------------------------------------------------------------------- /archive/.gitignore: -------------------------------------------------------------------------------- 1 | test_data 2 | -------------------------------------------------------------------------------- /archive/archive.go: -------------------------------------------------------------------------------- 1 | // archive features for goxc. Limited support for zip, tar.gz and ar archiving 2 | package archive 3 | 4 | /* 5 | Copyright 2013 Am Laher 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | import ( 21 | "path/filepath" 22 | 23 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 24 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 25 | "github.com/laher/goxc/config" 26 | "github.com/laher/goxc/core" 27 | ) 28 | 29 | // type definition representing a file to be archived. Details location on filesystem and destination filename inside archive. 30 | type ArchiveItem struct { 31 | //if FileSystemPath is empty, use Data instead 32 | FileSystemPath string 33 | ArchivePath string 34 | Data []byte 35 | } 36 | 37 | func ArchiveItemFromFileSystem(fileSystemPath, archivePath string) ArchiveItem { 38 | return ArchiveItem{fileSystemPath, archivePath, nil} 39 | } 40 | 41 | func ArchiveItemFromBytes(data []byte, archivePath string) ArchiveItem { 42 | return ArchiveItem{"", archivePath, data} 43 | } 44 | 45 | // type definition for different archiving implementations 46 | type Archiver func(archiveFilename string, itemsToArchive []ArchiveItem) error 47 | 48 | // goxc function to archive a binary along with supporting files (e.g. README or LICENCE). 49 | func ArchiveBinariesAndResources(outDir, platName string, binPaths []string, appName string, resources []string, settings config.Settings, archiver Archiver, ending string, includeTopLevelDir bool) (zipFilename string, err error) { 50 | var zipName string 51 | if settings.PackageVersion != "" && settings.PackageVersion != core.PACKAGE_VERSION_DEFAULT { 52 | //0.1.6 using appname_version_platform. See issue 3 53 | zipName = appName + "_" + settings.GetFullVersionName() + "_" + platName 54 | } else { 55 | zipName = appName + "_" + platName 56 | } 57 | zipFilename = filepath.Join(outDir, zipName+"."+ending) 58 | var zipDir string 59 | if includeTopLevelDir { 60 | zipDir = zipName 61 | } else { 62 | zipDir = "" 63 | } 64 | toArchive := []ArchiveItem{} 65 | for _, binPath := range binPaths { 66 | destFile := filepath.Base(binPath) 67 | if zipDir != "" { 68 | destFile = filepath.Join(zipDir, destFile) 69 | } 70 | toArchive = append(toArchive, ArchiveItemFromFileSystem(binPath, destFile)) 71 | } 72 | for _, resource := range resources { 73 | destFile := resource 74 | if zipDir != "" { 75 | destFile = filepath.Join(zipDir, destFile) 76 | } 77 | toArchive = append(toArchive, ArchiveItemFromFileSystem(resource, destFile)) 78 | } 79 | err = archiver(zipFilename, toArchive) 80 | return 81 | } 82 | -------------------------------------------------------------------------------- /archive/targz.go: -------------------------------------------------------------------------------- 1 | package archive 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "archive/tar" 21 | "compress/gzip" 22 | "io" 23 | "os" 24 | "path/filepath" 25 | "strings" 26 | "time" 27 | ) 28 | 29 | // TarGz implementation of Archiver. 30 | func TarGz(archiveFilename string, itemsToArchive []ArchiveItem) error { 31 | // file write 32 | fw, err := os.Create(archiveFilename) 33 | if err != nil { 34 | return err 35 | } 36 | defer fw.Close() 37 | 38 | // gzip write 39 | gw := gzip.NewWriter(fw) 40 | defer gw.Close() 41 | 42 | // tar write 43 | tw := tar.NewWriter(gw) 44 | defer tw.Close() 45 | 46 | for _, item := range itemsToArchive { 47 | err = addItemToTarGz(item, tw) 48 | if err != nil { 49 | return err 50 | } 51 | } 52 | err = tw.Close() 53 | return err 54 | } 55 | 56 | // Write a single file to TarGz 57 | func TarGzWrite(item ArchiveItem, tw *tar.Writer, fi os.FileInfo) (err error) { 58 | if item.FileSystemPath != "" { 59 | fr, err := os.Open(item.FileSystemPath) 60 | if err == nil { 61 | defer fr.Close() 62 | 63 | h := new(tar.Header) 64 | h.Name = item.ArchivePath 65 | h.Size = fi.Size() 66 | h.Mode = int64(fi.Mode()) 67 | h.ModTime = fi.ModTime() 68 | 69 | err = tw.WriteHeader(h) 70 | 71 | if err == nil { 72 | _, err = io.Copy(tw, fr) 73 | } 74 | } 75 | } else { 76 | h := new(tar.Header) 77 | //backslash-only paths 78 | h.Name = strings.Replace(item.ArchivePath, "\\", "/", -1) 79 | h.Size = int64(len(item.Data)) 80 | h.Mode = int64(0644) //? is this ok? 81 | h.ModTime = time.Now() 82 | err = tw.WriteHeader(h) 83 | if err == nil { 84 | _, err = tw.Write(item.Data) 85 | } 86 | } 87 | return err 88 | } 89 | 90 | func addItemToTarGz(item ArchiveItem, tw *tar.Writer) (err error) { 91 | if item.FileSystemPath != "" { 92 | fi, err := os.Stat(item.FileSystemPath) 93 | if err != nil { 94 | return err 95 | } 96 | if fi.IsDir() { 97 | err = addDirectoryToTarGz(item, tw) 98 | return err 99 | } 100 | err = TarGzWrite(item, tw, fi) 101 | } else { 102 | err = TarGzWrite(item, tw, nil) 103 | } 104 | return err 105 | } 106 | 107 | func addDirectoryToTarGz(dirPath ArchiveItem, tw *tar.Writer) error { 108 | dir, err := os.Open(dirPath.FileSystemPath) 109 | if err == nil { 110 | defer dir.Close() 111 | fis, err := dir.Readdir(0) 112 | if err == nil { 113 | for _, fi := range fis { 114 | curPath := ArchiveItemFromFileSystem(filepath.Join(dirPath.FileSystemPath, fi.Name()), filepath.Join(dirPath.ArchivePath, fi.Name())) 115 | err = addItemToTarGz(curPath, tw) 116 | if err != nil { 117 | return err 118 | } 119 | } 120 | } 121 | } 122 | return err 123 | } 124 | -------------------------------------------------------------------------------- /archive/zip.go: -------------------------------------------------------------------------------- 1 | package archive 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "archive/zip" 21 | "io" 22 | "os" 23 | "strings" 24 | ) 25 | 26 | //TODO: folder support 27 | func Zip(zipFilename string, itemsToArchive []ArchiveItem) error { 28 | zf, err := os.Create(zipFilename) 29 | if err != nil { 30 | return err 31 | } 32 | defer zf.Close() 33 | 34 | zw := zip.NewWriter(zf) 35 | defer zw.Close() 36 | 37 | //resources 38 | for _, item := range itemsToArchive { 39 | err = addFileToZIP(zw, item) 40 | if err != nil { 41 | return err 42 | } 43 | } 44 | err = zw.Close() 45 | return err 46 | } 47 | 48 | func addFileToZIP(zw *zip.Writer, item ArchiveItem) (err error) { 49 | binfo, err := os.Stat(item.FileSystemPath) 50 | if err != nil { 51 | return 52 | } 53 | header, err := zip.FileInfoHeader(binfo) 54 | if err != nil { 55 | return 56 | } 57 | header.Method = zip.Deflate 58 | //always use forward slashes even on Windows 59 | header.Name = strings.Replace(item.ArchivePath, "\\", "/", -1) 60 | w, err := zw.CreateHeader(header) 61 | if err != nil { 62 | zw.Close() 63 | return 64 | } 65 | bf, err := os.Open(item.FileSystemPath) 66 | if err != nil { 67 | return 68 | } 69 | defer bf.Close() 70 | _, err = io.Copy(w, bf) 71 | return 72 | } 73 | -------------------------------------------------------------------------------- /config/buildsettings.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/laher/goxc/typeutils" 7 | ) 8 | 9 | type BuildSettings struct { 10 | //GoRoot string `json:"-"` //Made *not* settable in settings file. Only at runtime. 11 | //GoVersion string `json:",omitempty"` //hmm. Should I encourage this? 12 | Processors *int `json:",omitempty"` 13 | Race *bool `json:",omitempty"` 14 | Verbose *bool `json:",omitempty"` 15 | PrintCommands *bool `json:",omitempty"` 16 | CcFlags *string `json:",omitempty"` 17 | Compiler *string `json:",omitempty"` 18 | GccGoFlags *string `json:",omitempty"` 19 | GcFlags *string `json:",omitempty"` 20 | InstallSuffix *string `json:",omitempty"` 21 | LdFlags *string `json:",omitempty"` 22 | LdFlagsXVars *map[string]interface{} `json:",omitempty"` 23 | Tags *string `json:",omitempty"` 24 | ExtraArgs []string `json:",omitempty"` 25 | } 26 | 27 | func (this BuildSettings) Equals(that BuildSettings) bool { 28 | return this.Processors == that.Processors && 29 | this.Race == that.Race && 30 | this.Verbose == that.Verbose && 31 | this.PrintCommands == that.PrintCommands && 32 | this.CcFlags == that.CcFlags && 33 | this.Compiler == that.Compiler && 34 | this.GccGoFlags == that.GccGoFlags && 35 | this.GcFlags == that.GcFlags && 36 | this.InstallSuffix == that.InstallSuffix && 37 | this.LdFlags == that.LdFlags && 38 | this.Tags == that.Tags && 39 | typeutils.StringSliceEquals(this.ExtraArgs, that.ExtraArgs) && 40 | ((this.LdFlagsXVars == nil && that.LdFlagsXVars == nil) || 41 | (this.LdFlagsXVars != nil && that.LdFlagsXVars != nil && typeutils.AreMapsEqual(*this.LdFlagsXVars, *that.LdFlagsXVars))) 42 | } 43 | func (this BuildSettings) IsEmpty() bool { 44 | bs := BuildSettings{} 45 | if bs.Equals(this) { 46 | return true 47 | } 48 | //defaults can also be considered 'empty' 49 | FillBuildSettingsDefaults(&bs) 50 | return bs.Equals(this) 51 | } 52 | 53 | func buildSettingsFromMap(m map[string]interface{}) (*BuildSettings, error) { 54 | var err error 55 | bs := BuildSettings{} 56 | FillBuildSettingsDefaults(&bs) 57 | for k, v := range m { 58 | switch k { 59 | //case "GoRoot": 60 | // bs.GoRoot, err = typeutils.ToString(v, k) 61 | case "Processors": 62 | var fp float64 63 | fp, err = typeutils.ToFloat64(v, k) 64 | if err == nil { 65 | processors := int(fp) 66 | bs.Processors = &processors 67 | } 68 | case "Race": 69 | var race bool 70 | race, err = typeutils.ToBool(v, k) 71 | if err == nil { 72 | bs.Race = &race 73 | } 74 | case "Verbose": 75 | var verbose bool 76 | verbose, err = typeutils.ToBool(v, k) 77 | if err == nil { 78 | bs.Verbose = &verbose 79 | } 80 | case "PrintCommands": 81 | var printCommands bool 82 | printCommands, err = typeutils.ToBool(v, k) 83 | if err == nil { 84 | bs.PrintCommands = &printCommands 85 | } 86 | case "CcFlags": 87 | var ccFlags string 88 | ccFlags, err = typeutils.ToString(v, k) 89 | if err == nil { 90 | bs.CcFlags = &ccFlags 91 | } 92 | case "Compiler": 93 | var s string 94 | s, err = typeutils.ToString(v, k) 95 | if err == nil { 96 | bs.Compiler = &s 97 | } 98 | case "GccGoFlags": 99 | var s string 100 | s, err = typeutils.ToString(v, k) 101 | if err == nil { 102 | bs.GccGoFlags = &s 103 | } 104 | case "GcFlags": 105 | var s string 106 | s, err = typeutils.ToString(v, k) 107 | if err == nil { 108 | bs.GcFlags = &s 109 | } 110 | case "InstallSuffix": 111 | var s string 112 | s, err = typeutils.ToString(v, k) 113 | if err == nil { 114 | bs.InstallSuffix = &s 115 | } 116 | case "LdFlags": 117 | var s string 118 | s, err = typeutils.ToString(v, k) 119 | if err == nil { 120 | bs.LdFlags = &s 121 | } 122 | case "Tags": 123 | var s string 124 | s, err = typeutils.ToString(v, k) 125 | if err == nil { 126 | bs.Tags = &s 127 | } 128 | case "LdFlagsXVars": 129 | var xVars map[string]interface{} 130 | xVars, err = typeutils.ToMap(v, k) 131 | if err == nil { 132 | bs.LdFlagsXVars = &xVars 133 | } 134 | case "ExtraArgs": 135 | bs.ExtraArgs, err = typeutils.ToStringSlice(v, k) 136 | default: 137 | log.Printf("Warning!! Unrecognised Setting '%s' (value %v)", k, v) 138 | } 139 | if err != nil { 140 | return &bs, err 141 | } 142 | 143 | } 144 | return &bs, err 145 | } 146 | -------------------------------------------------------------------------------- /config/cli.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | //tasks (and task settings) are defined as args after the main flags 9 | func ParseCliTasksAndTaskSettings(args []string) ([]string, map[string]map[string]interface{}, error) { 10 | tasks := []string{} 11 | taskSettings := map[string]map[string]interface{}{} 12 | lastTask := "" 13 | lastTaskSettingKey := "" 14 | for _, arg := range args { 15 | if lastTaskSettingKey != "" { 16 | taskSettings[lastTask][lastTaskSettingKey] = arg 17 | lastTaskSettingKey = "" 18 | } else if strings.HasPrefix(arg, "-") { 19 | _, exists := taskSettings[lastTask] 20 | if !exists { 21 | taskSettings[lastTask] = map[string]interface{}{} 22 | } 23 | if strings.Contains(arg, "=") { 24 | splut := strings.Split(arg, "=") 25 | key := splut[0][1:] 26 | //strip double-hyphen 27 | if strings.HasPrefix(key, "-") { 28 | key = key[1:] 29 | } 30 | val := splut[1] 31 | taskSettings[lastTask][key] = val 32 | } else { 33 | key := arg[1:] 34 | //strip double-hyphen 35 | if strings.HasPrefix(key, "-") { 36 | key = key[1:] 37 | } 38 | lastTaskSettingKey = key 39 | } 40 | 41 | } else { 42 | tasks = append(tasks, arg) 43 | lastTask = arg 44 | } 45 | } 46 | if lastTaskSettingKey != "" { 47 | return tasks, taskSettings, errors.New("Received a task setting with no value. Please at least use empty quotes") 48 | } 49 | //log.Printf("TaskSettings: %+v", taskSettings) 50 | return tasks, taskSettings, nil 51 | } 52 | -------------------------------------------------------------------------------- /config/cli_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/laher/goxc/typeutils" 7 | ) 8 | 9 | type TasksAndTaskSettings struct { 10 | Args []string 11 | Tasks []string 12 | TaskSettings map[string]map[string]interface{} 13 | ParseError error 14 | } 15 | 16 | var fixtures = []TasksAndTaskSettings{ 17 | {Args: []string{"task1", "-blah=1", "-wah=2"}, 18 | Tasks: []string{"task1"}, 19 | TaskSettings: map[string]map[string]interface{}{ 20 | "task1": map[string]interface{}{ 21 | "blah": "1", 22 | "wah": "2", 23 | }, 24 | }, 25 | }, 26 | {Args: []string{"task1", "-blah=1", "t2", "-wah=2"}, 27 | Tasks: []string{"task1", "t2"}, 28 | TaskSettings: map[string]map[string]interface{}{ 29 | "task1": map[string]interface{}{ 30 | "blah": "1", 31 | }, 32 | "t2": map[string]interface{}{ 33 | "wah": "2", 34 | }, 35 | }, 36 | }, 37 | } 38 | 39 | func TestParseCliTasksAndSettings(t *testing.T) { 40 | for _, fixture := range fixtures { 41 | t.Logf("Fixture: %+v", fixture.Args) 42 | tasks, taskSettings, err := ParseCliTasksAndTaskSettings(fixture.Args) 43 | if err != fixture.ParseError { 44 | t.Errorf("Error: '%v' differs from expected: %v", err, fixture.ParseError) 45 | } 46 | t.Logf("Tasks: %v", tasks) 47 | if !typeutils.StringSliceEquals(tasks, fixture.Tasks) { 48 | t.Errorf("Tasks %v not equal to expected %v", tasks, fixture.Tasks) 49 | } 50 | t.Logf("TaskSettings: %v", taskSettings) 51 | 52 | if !typeutils.AreMapStringMapStringInterfacesEqual(taskSettings, fixture.TaskSettings) { 53 | t.Errorf("TaskSettings %v not equal to expected %v", taskSettings, fixture.TaskSettings) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /config/defaults.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | 7 | "github.com/laher/goxc/core" 8 | ) 9 | 10 | func FillBuildSettingsDefaults(bs *BuildSettings) { 11 | bs.LdFlagsXVars = &map[string]interface{}{"TimeNow": "main.BUILD_DATE", "Version": "main.VERSION"} 12 | } 13 | 14 | //TODO fulfil all defaults 15 | func FillSettingsDefaults(settings *Settings, workingDirectory string) { 16 | if settings.AppName == "" { 17 | settings.AppName = core.GetAppName(settings.AppName, workingDirectory) 18 | } 19 | if settings.OutPath == "" { 20 | settings.OutPath = core.OUTFILE_TEMPLATE_DEFAULT 21 | } 22 | if settings.ResourcesInclude == "" { 23 | settings.ResourcesInclude = core.RESOURCES_INCLUDE_DEFAULT 24 | } 25 | if settings.ResourcesExclude == "" { 26 | settings.ResourcesExclude = core.RESOURCES_EXCLUDE_DEFAULT 27 | } 28 | if settings.MainDirsExclude == "" { 29 | settings.MainDirsExclude = core.MAIN_DIRS_EXCLUDE_DEFAULT 30 | } 31 | if settings.PackageVersion == "" { 32 | settings.PackageVersion = core.PACKAGE_VERSION_DEFAULT 33 | } 34 | if settings.BuildSettings == nil { 35 | bs := BuildSettings{} 36 | FillBuildSettingsDefaults(&bs) 37 | settings.BuildSettings = &bs 38 | } 39 | if settings.GoRoot == "" { 40 | if settings.IsVerbose() { 41 | log.Printf("Defaulting GoRoot to runtime.GOROOT (%s)", runtime.GOROOT()) 42 | } 43 | settings.GoRoot = runtime.GOROOT() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /config/json_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "log" 5 | "path/filepath" 6 | "testing" 7 | ) 8 | 9 | func TestStripEmpties(t *testing.T) { 10 | js := []byte(`{ 11 | "Verbosity" : "", 12 | "PackageVersion" : "0.1.1", 13 | "zipArchives" : false, 14 | "ArtifactsDest" : "../goxc-pages/", 15 | "platforms": ["windows/386"], 16 | "blah" : [] 17 | }`) 18 | 19 | outjs, err := StripEmpties(js, true) 20 | if err != nil { 21 | t.Fatalf("Error returned from StripEmpties: %v", err) 22 | } 23 | log.Printf("stripped: %v", string(outjs)) 24 | } 25 | func TestLoadSettings(t *testing.T) { 26 | js := []byte(`{ 27 | "Verbosity" : "v", 28 | "PackageVersion" : "0.1.1", 29 | "zipArchives" : false, 30 | "ArtifactsDest" : "../goxc-pages/", 31 | "platforms": "windows/386" 32 | }`) 33 | settings, err := readJson(js) 34 | if err != nil { 35 | t.Fatalf("Err: %v", err) 36 | } 37 | if !settings.IsVerbose() { 38 | t.Fatalf("Verbose flag not set!") 39 | } 40 | } 41 | 42 | func TestLoadJsonConfigsInvalid(t *testing.T) { 43 | _, err := LoadJsonConfigs("", []string{filepath.Join("testdata", "invalid.goxc.json")}, false) 44 | if err == nil { 45 | t.Fatalf("invalid.goxc.json was loaded without an error, despite being invalid") 46 | } 47 | } 48 | 49 | /* 50 | func TestLoadFile(t *testing.T) { 51 | file := "goxc.json" 52 | settings, err := LoadJsonFile(file, true) 53 | if err != nil { 54 | t.Fatalf("Err: %v", err) 55 | } else { 56 | log.Printf("settings: %v", settings) 57 | } 58 | } 59 | 60 | func TestLoadParentFile(t *testing.T) { 61 | file := "../goxc.json" 62 | settings, err := LoadJsonFile(file, true) 63 | if err != nil { 64 | t.Fatalf("Err: %v", err) 65 | } else { 66 | log.Printf("settings: %v", settings) 67 | } 68 | } 69 | */ 70 | -------------------------------------------------------------------------------- /config/sliceflags.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Define a type named "intslice" as a slice of ints 8 | type Strslice []string 9 | 10 | // Now, for our new type, implement the two methods of 11 | // the flag.Value interface... 12 | // The first method is String() string 13 | func (i *Strslice) String() string { 14 | return fmt.Sprintf("%v", *i) 15 | } 16 | 17 | // The second method is Set(value string) error 18 | func (i *Strslice) Set(value string) error { 19 | fmt.Printf("Adding %s\n", value) 20 | *i = append(*i, value) 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /config/test-goxc.json: -------------------------------------------------------------------------------- 1 | { "Settings" : { 2 | "ArtifactsDest" : "../goxc-pages/dl", 3 | "Verbosity" : "v", 4 | "artifactVersion" : "0.1.1", 5 | "zipArchives" : false, 6 | "ArtifactsDest" : "../goxc-pages/", 7 | "platforms": "windows/386" 8 | } } 9 | -------------------------------------------------------------------------------- /config/testdata/invalid.goxc.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppName": "example", 3 | "ArtifactsDest": "release", 4 | "OutPath": "foo", 5 | "Tasks": [ 6 | "xc" 7 | ], 8 | "Arch": "amd64", 9 | "Os": "linux darwin", 10 | "MainDirsExclude": "bar", 11 | "ConfigVersion": "0.9", 12 | "BuildSettings": { 13 | "LdFlagsXVars": { 14 | "Version": "baz.Version" 15 | }, 16 | "Tags": "important", 17 | "ExtraArgs": "-a" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/constants.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | //messages ... int8n? 20 | const ( 21 | // Message to install go from source, incase it is missing 22 | MSG_INSTALL_GO_FROM_SOURCE = "goxc requires Go to be installed from Source. Please follow instructions at http://golang.org/doc/install/source" 23 | ) 24 | 25 | //defaults ... 26 | const ( 27 | ARTIFACTS_DEST_TEMPLATE_DEFAULT = "{{.GoBin}}{{.PS}}{{.AppName}}-xc" 28 | OUTFILE_TEMPLATE_DEFAULT = "{{.Dest}}{{.PS}}{{.Version}}{{.PS}}{{.Os}}_{{.Arch}}{{.PS}}{{.ExeName}}{{.Ext}}" 29 | OUTFILE_TEMPLATE_FORMARKDOWN = "{{.Dest}}{{.PS}}{{.Os}}_{{.Arch}}{{.PS}}{{.ExeName}}{{.Ext}}" 30 | BUILD_CONSTRAINTS_DEFAULT = "" 31 | CODESIGN_DEFAULT = "" 32 | 33 | // Default resources to include. Comma-separated list of globs. 34 | RESOURCES_INCLUDE_DEFAULT = "INSTALL*,README*,LICENSE*" 35 | RESOURCES_EXCLUDE_DEFAULT = "*.go" //TODO 36 | // Main dirs to exclude by default (Godeps!) 37 | MAIN_DIRS_EXCLUDE_DEFAULT = "Godeps,testdata,_project,vendor" 38 | 39 | OS_DEFAULT = "" 40 | ARCH_DEFAULT = "" 41 | PACKAGE_VERSION_DEFAULT = "snapshot" 42 | PRERELEASE_INFO_DEFAULT = "SNAPSHOT" 43 | BRANCH_ORIGINAL = "original" 44 | 45 | VerbosityDefault = "d" 46 | VerbosityQuiet = "q" //TODO 47 | VerbosityVerbose = "v" 48 | //0.4 removed in favour of associated tasks 49 | //ARTIFACT_TYPE_ZIP = "zip" 50 | //ARTIFACT_TYPE_BIN = "bin" 51 | 52 | GOXC_FILE_EXT = ".goxc.json" 53 | GOXC_LOCAL_FILE_EXT = ".goxc.local.json" 54 | GOXC_CONFIGNAME_DEFAULT = "default" 55 | GOXC_CONFIGNAME_BASE = "" 56 | 57 | //taskname required by config/json 58 | TASK_BUILD_TOOLCHAIN = "toolchain" 59 | //windows required inside core methods 60 | WINDOWS = "windows" 61 | ) 62 | -------------------------------------------------------------------------------- /core/core.go: -------------------------------------------------------------------------------- 1 | //Some general utility functions for goxc 2 | package core 3 | 4 | /* 5 | Copyright 2013 Am Laher 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | import ( 21 | "bytes" 22 | "errors" 23 | "fmt" 24 | "log" 25 | "os" 26 | "os/user" 27 | "path/filepath" 28 | "runtime" 29 | "strings" 30 | "text/template" 31 | ) 32 | 33 | const () 34 | 35 | // get the path to the 'make' batch script within the GO source tree. 36 | // i.e. runtime.GOOS / src / make.bat|bash 37 | func GetMakeScriptPath(goroot string) string { 38 | gohostos := runtime.GOOS 39 | var scriptname string 40 | if gohostos == WINDOWS { 41 | scriptname = "make.bat" 42 | } else { 43 | scriptname = "make.bash" 44 | } 45 | return filepath.Join(goroot, "src", scriptname) 46 | } 47 | 48 | // Basic system sanity check. Checks GOROOT is set and 'make' batch script exists. 49 | // TODO: in future this could check for existence of gcc/mingw/alternative 50 | func SanityCheck(goroot string) error { 51 | if goroot == "" { 52 | goroot = runtime.GOROOT() 53 | } 54 | scriptpath := GetMakeScriptPath(goroot) 55 | _, err := os.Stat(scriptpath) 56 | if err != nil { 57 | if os.IsNotExist(err) { 58 | return errors.New(fmt.Sprintf("Make script ('%s') does not exist!", scriptpath)) 59 | } else { 60 | return errors.New(fmt.Sprintf("Error reading make script ('%s'): %v", scriptpath, err)) 61 | } 62 | } 63 | return nil 64 | } 65 | 66 | // simple fileExists method which inspects the error from os.Stat 67 | func FileExists(path string) (bool, error) { 68 | _, err := os.Stat(path) 69 | if err == nil { 70 | return true, nil 71 | } 72 | if os.IsNotExist(err) { 73 | return false, nil 74 | } 75 | return false, err 76 | } 77 | 78 | func ParseCommaGlobs(commaGlob string) []string { 79 | if commaGlob == "" { 80 | return []string{} 81 | } 82 | globs := strings.Split(commaGlob, ",") 83 | //normalize 84 | //treat slashes/backslashes as the same thing ... 85 | for i, glob := range globs { 86 | glob := strings.Replace(glob, "/", string(os.PathSeparator), -1) 87 | glob = strings.Replace(glob, "\\", string(os.PathSeparator), -1) 88 | globs[i] = glob 89 | } 90 | return globs 91 | } 92 | func resolveToFiles(item string) ([]string, error) { 93 | fi, err := os.Lstat(item) 94 | if err != nil { 95 | return []string{}, err 96 | } 97 | if fi.IsDir() { 98 | files, err := dirToFiles(item) 99 | return files, err 100 | } else { 101 | return []string{item}, nil 102 | } 103 | } 104 | func dirToFiles(dir string) ([]string, error) { 105 | files := []string{} 106 | err := filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { 107 | if fi != nil && !fi.IsDir() { 108 | files = append(files, path) 109 | } 110 | return nil 111 | }) 112 | return files, err 113 | } 114 | 115 | // Glob parser for 'Include resources' 116 | // TODO generalise for exclude resources and any other globs. 117 | func ParseIncludeResources(basedir, includeResources, excludeResources string, isVerbose bool) []string { 118 | allMatches := []string{} 119 | if includeResources != "" { 120 | resourceGlobs := ParseCommaGlobs(includeResources) 121 | if isVerbose { 122 | log.Printf("IncludeGlobs: %v", resourceGlobs) 123 | } 124 | excludeGlobs := ParseCommaGlobs(excludeResources) 125 | if isVerbose { 126 | log.Printf("ExcludeGlobs: %v", excludeGlobs) 127 | } 128 | for _, resourceGlob := range resourceGlobs { 129 | matches, err := filepath.Glob(filepath.Join(basedir, resourceGlob)) 130 | if err != nil { 131 | //ignore this inclusion glob 132 | log.Printf("GLOB error: %s: %s", resourceGlob, err) 133 | } else { 134 | for _, match := range matches { 135 | files, err := resolveToFiles(match) 136 | 137 | if err != nil { 138 | //ignore this match 139 | log.Printf("dir lookup error: %s: %s", match, err) 140 | } else { 141 | for _, file := range files { 142 | exclude := false 143 | for _, excludeGlob := range excludeGlobs { 144 | //incomplete!! 145 | if !strings.Contains(excludeGlob, string(os.PathSeparator)) { 146 | excludeGlob = filepath.Join(filepath.Dir(file), excludeGlob) 147 | } 148 | excludedThisTime, err := filepath.Match(excludeGlob, file) 149 | if isVerbose { 150 | log.Printf("Globbing %s for exclusion %s", file, excludeGlob) 151 | } 152 | if err != nil { 153 | //ignore this exclusion glob 154 | log.Printf("Exclude-GLOB error: %s: %s", excludeGlob, err) 155 | } 156 | if excludedThisTime { 157 | if isVerbose { 158 | log.Printf("Excluded: %s with %s", file, excludeGlob) 159 | } 160 | exclude = true 161 | } 162 | } 163 | if !exclude { 164 | //return relative filename 165 | relativeFilename, err := filepath.Rel(basedir, file) 166 | if err != nil { 167 | log.Printf("Warning: file %s is not inside %s", file, basedir) 168 | allMatches = append(allMatches, file) 169 | } else { 170 | allMatches = append(allMatches, relativeFilename) 171 | } 172 | } 173 | } 174 | } 175 | } 176 | } 177 | } 178 | } 179 | if isVerbose { 180 | log.Printf("Resources to include: %v", allMatches) 181 | } 182 | return allMatches 183 | 184 | } 185 | 186 | // Get application name (defaults to dirname) 187 | func GetAppName(specifiedAppName, workingDirectory string) string { 188 | if specifiedAppName != "" { 189 | return specifiedAppName 190 | } 191 | appDirname, err := filepath.Abs(workingDirectory) 192 | if err != nil { 193 | log.Printf("Error: %v", err) 194 | } 195 | appName := filepath.Base(appDirname) 196 | return appName 197 | } 198 | 199 | // Tries to find the most relevant GOPATH element. 200 | // First, tries to find an element which is a parent of the current directory. 201 | // If not, it uses the first one. 202 | func GetGoPathElement(workingDirectory string) string { 203 | //build.Import(path, srcDir string, mode ImportMode) (*Package, error) 204 | var gopath string 205 | gopathVar := os.Getenv("GOPATH") 206 | if gopathVar == "" { 207 | log.Printf("GOPATH env variable not set! Using '.'") 208 | gopath = "." 209 | } else { 210 | gopaths := filepath.SplitList(gopathVar) 211 | validGopaths := []string{} 212 | workingDirectoryAbs, err := filepath.Abs(workingDirectory) 213 | //log.Printf("workingDirectory %s, (abs) %s", workingDirectory, workingDirectoryAbs) 214 | if err != nil { 215 | //strange. TODO: investigate 216 | workingDirectoryAbs = workingDirectory 217 | } 218 | //see if you can match the workingDirectory 219 | for _, gopathi := range gopaths { 220 | //if empty or GOROOT, continue 221 | //logic taken from http://tip.golang.org/src/pkg/go/build/build.go 222 | if gopathi == "" || gopathi == runtime.GOROOT() || strings.HasPrefix(gopathi, "~") { 223 | continue 224 | } else { 225 | validGopaths = append(validGopaths, gopathi) 226 | } 227 | gopathAbs, err := filepath.Abs(gopathi) 228 | if err != nil { 229 | //strange. TODO: investigate 230 | gopathAbs = gopathi 231 | } 232 | //log.Printf("gopath element %s, (abs) %s", gopathi, gopathAbs) 233 | //working directory is inside this path element. Use it! 234 | if strings.HasPrefix(workingDirectoryAbs, gopathAbs) { 235 | return gopathi 236 | } 237 | } 238 | if len(validGopaths) > 0 { 239 | gopath = validGopaths[0] 240 | 241 | } else { 242 | log.Printf("GOPATH env variable not valid! Using '.'") 243 | gopath = "." 244 | } 245 | } 246 | return gopath 247 | } 248 | 249 | func GetOutDestRoot(appName string, workingDirectory string, templateText string) (string, error) { 250 | if templateText == "" { 251 | templateText = ARTIFACTS_DEST_TEMPLATE_DEFAULT 252 | } 253 | var outDestRoot string 254 | tmpl, err := template.New("rootTemplate").Parse(templateText) 255 | goBin := GoBin(workingDirectory) 256 | homeDir := UserHomeDir() 257 | myGoPath := GetGoPathElement(workingDirectory) 258 | data := RootDirVars{goBin, myGoPath, homeDir, appName, string(os.PathSeparator)} 259 | var out bytes.Buffer 260 | err = tmpl.Execute(&out, data) 261 | if err != nil { 262 | return "", err 263 | } 264 | outDestRoot = out.String() 265 | //normaliseAbs 266 | if strings.HasPrefix(outDestRoot, "~/") { 267 | outDestRoot = strings.Replace(outDestRoot, "~", UserHomeDir(), 1) 268 | } 269 | outDestRootAbs, err := filepath.Abs(outDestRoot) 270 | if err != nil { 271 | log.Printf("Error resolving absolute filename") 272 | return outDestRoot, nil 273 | } else { 274 | return outDestRootAbs, nil 275 | } 276 | } 277 | 278 | func GoBin(workingDirectory string) string { 279 | gobin := os.Getenv("GOBIN") 280 | if gobin == "" { 281 | gopath := GetGoPathElement(workingDirectory) 282 | // follow usual GO rules for making GOBIN 283 | gobin = filepath.Join(gopath, "bin") 284 | } 285 | return gobin 286 | } 287 | 288 | func UserHomeDir() string { 289 | usr, err := user.Current() 290 | if err != nil { 291 | log.Printf("Could not get home directory: %s", err) 292 | return os.Getenv("HOME") 293 | } 294 | //log.Printf("user dir: %s", usr.HomeDir) 295 | return usr.HomeDir 296 | } 297 | 298 | type RootDirVars struct { 299 | GoBin string 300 | MyGoPath string 301 | Home string 302 | AppName string 303 | PS string 304 | } 305 | type BinNameVars struct { 306 | RootDirVars 307 | Dest string 308 | ExeName string 309 | Version string 310 | Os string 311 | Arch string 312 | Ext string 313 | } 314 | 315 | func GetAbsoluteBin(goos, arch string, appName, exeName, workingDirectory, fullVersionName, templateText string, artifactsDestSetting string) (string, error) { 316 | tmpl, err := template.New("binTemplate").Parse(templateText) 317 | if err != nil { 318 | return "", err 319 | } 320 | var ending = "" 321 | if goos == WINDOWS { 322 | ending = ".exe" 323 | } 324 | rootTemplate := artifactsDestSetting 325 | 326 | root, err := GetOutDestRoot(appName, workingDirectory, rootTemplate) 327 | goBin := GoBin(workingDirectory) 328 | homeDir := UserHomeDir() 329 | myGoPath := GetGoPathElement(workingDirectory) 330 | rdv := RootDirVars{goBin, myGoPath, homeDir, appName, string(os.PathSeparator)} 331 | data := BinNameVars{rdv, root, exeName, fullVersionName, goos, arch, ending} 332 | var out bytes.Buffer 333 | err = tmpl.Execute(&out, data) 334 | if err != nil { 335 | return "", err 336 | } 337 | return out.String(), nil 338 | } 339 | 340 | // Check if slice contains a string. 341 | // DEPRECATED: use equivalent func inside typeutils. 342 | func ContainsString(h []string, n string) bool { 343 | for _, e := range h { 344 | if e == n { 345 | return true 346 | } 347 | } 348 | return false 349 | } 350 | -------------------------------------------------------------------------------- /core/core_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path" 7 | "path/filepath" 8 | "runtime" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | //just for test-driving the 'test' task 14 | /* 15 | func TestFail(t *testing.T) { 16 | t.Fatalf("FAIL") 17 | } 18 | */ 19 | 20 | func TestGetGoPath(t *testing.T) { 21 | orig := os.Getenv("GOPATH") 22 | p1 := path.Join("a", "b") 23 | 24 | os.Setenv("GOPATH", JoinList(p1, "..", "c")) 25 | gopath := GetGoPathElement(".") 26 | if gopath != ".." { 27 | t.Fatalf("Could not load gopath correctly 1 - %s %s", os.Getenv("GOPATH"), gopath) 28 | 29 | } 30 | os.Setenv("GOPATH", p1) 31 | gopath = GetGoPathElement(".") 32 | if gopath != p1 { 33 | t.Fatalf("Could not load gopath correctly 2 - %s %s", os.Getenv("GOPATH"), gopath) 34 | } 35 | os.Setenv("GOPATH", orig) 36 | } 37 | 38 | func JoinList(elem ...string) string { 39 | for i, e := range elem { 40 | if e != "" { 41 | return filepath.Clean(strings.Join(elem[i:], string(os.PathListSeparator))) 42 | } 43 | } 44 | return "" 45 | } 46 | 47 | func TestSanityCheck(t *testing.T) { 48 | //goroot := runtime.GOROOT() 49 | err := SanityCheck("i") 50 | if err == nil { 51 | t.Fatalf("sanity check failed! Expected to flag incorrect GOROOT variable") 52 | } 53 | tmpDir, err := ioutil.TempDir("", "goxc_test_sanityCheck") 54 | defer os.RemoveAll(tmpDir) 55 | err = SanityCheck(tmpDir) 56 | if err == nil { 57 | t.Fatalf("sanity check failed! Expected to notice missing src folder") 58 | } 59 | 60 | srcDir := filepath.Join(tmpDir, "src") 61 | os.Mkdir(srcDir, 0700) 62 | scriptname := GetMakeScriptPath(tmpDir) 63 | ioutil.WriteFile(scriptname, []byte("1"), 0111) 64 | err = SanityCheck(tmpDir) 65 | if err != nil { 66 | t.Fatalf("sanity check failed! Did not find src folder: %v", err) 67 | } 68 | //chmod doesnt work in Windows. 69 | //TODO: verify which OSes support chmod 70 | if runtime.GOOS == "linux" { 71 | os.Chmod(srcDir, 0600) 72 | defer os.Chmod(srcDir, 0700) 73 | err = SanityCheck(tmpDir) 74 | if err == nil { 75 | t.Fatalf("sanity check failed! Expected NOT to be able to open src folder") 76 | } 77 | } 78 | } 79 | 80 | func TestGoVersion(t *testing.T) { 81 | v := runtime.Version() 82 | t.Logf("Version is %s", v) 83 | } 84 | -------------------------------------------------------------------------------- /darwin.goxc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ArtifactsDest": "", 3 | "BuildConstraints": "darwin", 4 | "Resources": { 5 | "Include": "myosxfile1,myosxfile2" 6 | }, 7 | "FormatVersion": "0.8" 8 | } -------------------------------------------------------------------------------- /deb-contents/etc/init.d/dummy: -------------------------------------------------------------------------------- 1 | 2 | echo "dummy service" 3 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | goxc () unreleased; urgency=low 2 | 3 | * Initial import 4 | 5 | -- Am Laher Fri, 08 Aug 2014 17:07:48 +1200 -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: goxc 2 | Section: devel 3 | Priority: extra 4 | Maintainer: Am Laher 5 | Build-Depends: debhelper (>= 9.1.0), golang-go 6 | Standards-Version: 3.9.4 7 | 8 | Package: goxc 9 | Architecture: any 10 | Description: build tool for go 11 | goxc cross-compiles and packages go programs 12 | 13 | Package: goxc-dev 14 | Architecture: all 15 | Description: build tool for go - development package 16 | goxc cross-compiles and packages go programs 17 | 18 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Files: * 2 | Copyright: 2014 Am Laher 3 | License: BSD-3-clause 4 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | # Uncomment this to turn on verbose mode. 5 | #export DH_VERBOSE=1 6 | 7 | export GOPATH=$(CURDIR)::/usr/share/gocode 8 | 9 | PKGDIR=debian/goxc 10 | 11 | %: 12 | dh $@ 13 | 14 | clean: 15 | dh_clean 16 | rm -rf $(CURDIR)/bin/* $(CURDIR)/pkg/* 17 | rm -f $(CURDIR)/goinstall.log 18 | 19 | binary-arch: clean 20 | dh_prep 21 | dh_installdirs 22 | cd $(CURDIR)/src && go install ./... 23 | 24 | mkdir -p $(PKGDIR)/usr/bin $(CURDIR)/bin/ 25 | mkdir -p $(PKGDIR)/usr/share/gopkg/ $(CURDIR)/pkg/ 26 | 27 | BINFILES=$(wildcard $(CURDIR)/bin/*) 28 | 29 | for x in$(BINFILES); do \ 30 | cp $$x $(PKGDIR)/usr/bin/; \ 31 | done; 32 | 33 | PKGFILES=$(wildcard $(CURDIR)/pkg/*.a) 34 | for x in$(PKGFILES); do \ 35 | cp $$x $(PKGDIR)/usr/share/gopkg/; \ 36 | done; 37 | 38 | dh_strip 39 | dh_compress 40 | dh_fixperms 41 | dh_installdeb 42 | dh_gencontrol 43 | dh_md5sums 44 | dh_builddeb 45 | 46 | binary: binary-arch -------------------------------------------------------------------------------- /downloads.htpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{.AppName}} downloads (version {{.Version}}) 4 | 5 | 6 |

{{.AppName}} downloads

7 | 8 |

Version {{.Version}}

9 | 10 |
11 | NOTE: don't use binaries for goxc. Please use `go get -u github.com/laher/goxc` instead. 12 | 13 | {{range $k, $v := .Categories}}

{{$k}}

14 |
    15 | {{range $v}} 16 |
  • {{.Text}}
  • {{end}} 17 |
18 | {{end}} 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /downloads.tpl: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Downloads 4 | --- 5 | 6 | NOTE: don't use binaries for goxc. Please use `go get -u github.com/laher/goxc` instead. 7 | 8 | {{.AppName}} downloads (version {{.Version}}) 9 | 10 | {{range $k, $v := .Categories}}### {{$k}} 11 | 12 | {{range $v}} * [{{.Text}}]({{.RelativeLink}}) 13 | {{end}} 14 | {{end}} 15 | 16 | --- 17 | {{.ExtraVars.footer}} 18 | -------------------------------------------------------------------------------- /executils/exec.go: -------------------------------------------------------------------------------- 1 | // Utilities for invoking exec. Mainly focused on 'go build' and cross-compilation 2 | package executils 3 | 4 | /* 5 | Copyright 2013 Am Laher 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | import ( 21 | "bytes" 22 | "errors" 23 | "fmt" 24 | "io" 25 | "log" 26 | "os" 27 | "os/exec" 28 | "path/filepath" 29 | "runtime" 30 | "strconv" 31 | "strings" 32 | "text/template" 33 | "time" 34 | 35 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 36 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 37 | "github.com/laher/goxc/config" 38 | "github.com/laher/goxc/platforms" 39 | ) 40 | 41 | var ( 42 | BUILD_COMMANDS = []string{"build", "install"} 43 | ) 44 | 45 | // get list of args to be used in variable interpolation 46 | // ldflags are used in any to any build-related go task (install,build,test) 47 | /* 48 | func GetLdFlagVersionArgs(fullVersionName string) string { 49 | input := map[string]interface{}{"main.BUILD_DATE": time.Now().Format(time.RFC3339)} 50 | if fullVersionName != "" { 51 | input["main.VERSION"] = fullVersionName 52 | } 53 | return GetInterpolationFlags(input, "-X") 54 | } 55 | */ 56 | func buildInterpolationVars(args map[string]interface{}, fullVersionName string) map[string]interface{} { 57 | ret := map[string]interface{}{} 58 | for k, v := range args { 59 | switch typedV := v.(type) { 60 | case string: 61 | switch k { 62 | case "Version": 63 | ret[typedV] = fullVersionName 64 | case "TimeNow": 65 | ret[typedV] = time.Now().Format(time.RFC3339) 66 | } 67 | 68 | default: 69 | //error here? 70 | } 71 | } 72 | return ret 73 | } 74 | 75 | // get list of args to be used in e.g. ldflags variable interpolation 76 | // v0.9 changed from ldflags-specific to more general flag building 77 | func buildFlags(args map[string]interface{}, flag string) string { 78 | if len(args) < 1 { 79 | return "" 80 | } 81 | //ret := make([]string, len(args)) 82 | var buf bytes.Buffer 83 | for k, v := range args { 84 | switch typedV := v.(type) { 85 | case string: 86 | if strings.Contains(typedV, " ") { 87 | typedV = "'" + typedV + "'" 88 | } 89 | _, err := buf.WriteString(flag + " " + k + "=" + typedV + " ") 90 | if err != nil { 91 | log.Printf("Error writing flags") 92 | } 93 | default: 94 | _, err := buf.WriteString(fmt.Sprintf("%s %s='%v' ", flag, k, typedV)) 95 | if err != nil { 96 | log.Printf("Error writing flags") 97 | } 98 | } 99 | } 100 | return buf.String() 101 | } 102 | 103 | func isBuildCommand(subCmd string) bool { 104 | for _, buildCmd := range BUILD_COMMANDS { 105 | if subCmd == buildCmd { 106 | return true 107 | } 108 | } 109 | return false 110 | } 111 | 112 | func splitEnvVar(asString string) (string, string, error) { 113 | parts := strings.SplitN(asString, "=", 2) 114 | if len(parts) > 1 { 115 | return parts[0], parts[1], nil 116 | } else { 117 | return "", "", errors.New("Invalid env variable definition") 118 | } 119 | } 120 | 121 | // invoke the go command via the os/exec package 122 | // 0.3.1 123 | // v0.9 changed signature 124 | func InvokeGo(workingDirectory string, subCmd string, subCmdArgs []string, env []string, settings *config.Settings) error { 125 | fullVersionName := settings.GetFullVersionName() 126 | //var buildSettings config.BuildSettings 127 | buildSettings := settings.BuildSettings 128 | goRoot := settings.GoRoot 129 | if settings.IsVerbose() { 130 | log.Printf("build settings: %s", goRoot) 131 | } 132 | cmdPath := filepath.Join(goRoot, "bin", "go") 133 | args := []string{subCmd} 134 | 135 | //these features only apply to `go build` & `go install` 136 | if isBuildCommand(subCmd) { 137 | if buildSettings.Processors != nil { 138 | args = append(args, "-p", strconv.Itoa(*buildSettings.Processors)) 139 | } 140 | if buildSettings.Race != nil && *buildSettings.Race { 141 | args = append(args, "-race") 142 | } 143 | if buildSettings.Verbose != nil && *buildSettings.Verbose { 144 | args = append(args, "-v") 145 | } 146 | if buildSettings.PrintCommands != nil && *buildSettings.PrintCommands { 147 | args = append(args, "-x") 148 | } 149 | if buildSettings.CcFlags != nil && *buildSettings.CcFlags != "" { 150 | args = append(args, "-ccflags", *buildSettings.CcFlags) 151 | } 152 | if buildSettings.Compiler != nil && *buildSettings.Compiler != "" { 153 | args = append(args, "-compiler", *buildSettings.Compiler) 154 | } 155 | if buildSettings.GccGoFlags != nil && *buildSettings.GccGoFlags != "" { 156 | args = append(args, "-gccgoflags", *buildSettings.GccGoFlags) 157 | } 158 | if buildSettings.GcFlags != nil && *buildSettings.GcFlags != "" { 159 | args = append(args, "-gcflags", *buildSettings.GcFlags) 160 | } 161 | if buildSettings.InstallSuffix != nil && *buildSettings.InstallSuffix != "" { 162 | args = append(args, "-installsuffix", *buildSettings.InstallSuffix) 163 | } 164 | ldflags := "" 165 | if buildSettings.LdFlags != nil { 166 | ldflags = *buildSettings.LdFlags 167 | } 168 | if buildSettings.LdFlagsXVars != nil { 169 | //TODO! 170 | ldflags = ldflags + " " + buildFlags(buildInterpolationVars(*buildSettings.LdFlagsXVars, fullVersionName), "-X") 171 | } else { 172 | log.Printf("WARNING: LdFlagsXVars is nil. Not passing package version into compiler") 173 | } 174 | if ldflags != "" { 175 | args = append(args, "-ldflags", ldflags) 176 | } 177 | if buildSettings.Tags != nil && *buildSettings.Tags != "" { 178 | args = append(args, "-tags", *buildSettings.Tags) 179 | } 180 | if len(buildSettings.ExtraArgs) > 0 { 181 | args = append(args, buildSettings.ExtraArgs...) 182 | } 183 | } 184 | if settings.IsVerbose() { 185 | log.Printf("Env: %v", settings.Env) 186 | } 187 | if len(settings.Env) > 0 { 188 | vars := struct { 189 | PS string 190 | PLS string 191 | Env map[string]string 192 | }{ 193 | string(os.PathSeparator), 194 | string(os.PathListSeparator), 195 | map[string]string{}, 196 | } 197 | for _, val := range os.Environ() { 198 | k, v, err := splitEnvVar(val) 199 | if err != nil { 200 | //ignore invalid env vars from environment 201 | } else { 202 | vars.Env[k] = v 203 | } 204 | } 205 | for _, envTpl := range settings.Env { 206 | if settings.IsVerbose() { 207 | log.Printf("Processing env var %s", envTpl) 208 | } 209 | tpl, err := template.New("envItem").Parse(envTpl) 210 | if err != nil { 211 | return err 212 | } 213 | var dest bytes.Buffer 214 | err = tpl.Execute(&dest, vars) 215 | if err != nil { 216 | return err 217 | } 218 | executed := dest.String() 219 | if settings.IsVerbose() { 220 | if envTpl != executed { 221 | log.Printf("Setting env var (converted from %s to %s)", envTpl, executed) 222 | } else { 223 | log.Printf("Setting env var from config: %s", executed) 224 | } 225 | } 226 | env = append(env, dest.String()) 227 | //new address if necessary 228 | k, v, err := splitEnvVar(dest.String()) 229 | if err != nil { 230 | //fail on badly specified ENV vars 231 | return errors.New("Invalid env var defined by settings") 232 | } else { 233 | vars.Env[k] = v 234 | } 235 | } 236 | } 237 | args = append(args, subCmdArgs...) 238 | cmd, err := NewCmd(cmdPath, workingDirectory, args, env, settings.IsVerbose(), !settings.IsQuiet()) 239 | if err != nil { 240 | return err 241 | } 242 | if settings.IsVerbose() { 243 | log.Printf("invoking '%s %v' from '%s'", cmdPath, PrintableArgs(args), workingDirectory) 244 | } 245 | 246 | err = StartAndWait(cmd) 247 | if err != nil { 248 | log.Printf("'go' returned error: %s", err) 249 | return err 250 | } 251 | if settings.IsVerbose() { 252 | log.Printf("'go' completed successfully") 253 | } 254 | return nil 255 | 256 | } 257 | 258 | func NewCmd(cmdPath string, workingDirectory string, args []string, env []string, isVerbose bool, isRedirectToStdout bool) (*exec.Cmd, error) { 259 | cmd := exec.Command(cmdPath) 260 | if isRedirectToStdout { 261 | RedirectIO(cmd) 262 | } 263 | return cmd, PrepareCmd(cmd, workingDirectory, args, env, isVerbose) 264 | } 265 | 266 | func PrepareCmd(cmd *exec.Cmd, workingDirectory string, args []string, env []string, isVeryVerbose bool) error { 267 | cmd.Args = append(cmd.Args, args...) 268 | cmd.Dir = workingDirectory 269 | cmd.Env = CombineActualEnv(append(cmd.Env, env...), isVeryVerbose) 270 | return nil 271 | } 272 | 273 | // StartAndWait starts the given command and waits for it to exit. If the 274 | // command started successfully but exited with an error, any output to stderr 275 | // is included in the error message. 276 | func StartAndWait(cmd *exec.Cmd) error { 277 | stderr := &bytes.Buffer{} 278 | if cmd.Stderr == nil { 279 | cmd.Stderr = stderr 280 | } else { 281 | cmd.Stderr = io.MultiWriter(cmd.Stderr, stderr) 282 | } 283 | err := cmd.Start() 284 | if err != nil { 285 | return fmt.Errorf("Launch error: %s", err) 286 | } else { 287 | err = cmd.Wait() 288 | if err != nil { 289 | if stderr.Len() > 0 { 290 | return fmt.Errorf("Wait error: %s: %s", err, strings.TrimSpace(stderr.String())) 291 | } 292 | return fmt.Errorf("Wait error: %s", err) 293 | } 294 | return err 295 | } 296 | } 297 | 298 | func CombineActualEnv(env []string, isVeryVerbose bool) []string { 299 | //0.7.4 env replaces os.Environ 300 | cmdEnv := []string{} 301 | cmdEnv = append(cmdEnv, env...) 302 | for _, thisProcessEnvItem := range os.Environ() { 303 | thisProcessEnvItemSplit := strings.Split(thisProcessEnvItem, "=") 304 | key := thisProcessEnvItemSplit[0] 305 | exists := false 306 | for _, specifiedEnvItem := range env { 307 | specifiedEnvItemSplit := strings.Split(specifiedEnvItem, "=") 308 | specifiedEnvKey := specifiedEnvItemSplit[0] 309 | if specifiedEnvKey == key { 310 | if isVeryVerbose { 311 | log.Printf("Overriding ENV variable (%s replaces %s)", specifiedEnvItem, thisProcessEnvItem) 312 | } 313 | exists = true 314 | } 315 | } 316 | if !exists { 317 | cmdEnv = append(cmdEnv, thisProcessEnvItem) 318 | } 319 | } 320 | if isVeryVerbose { 321 | log.Printf("(verbose!) all env vars for 'go': %s", cmdEnv) 322 | if env != nil && len(env) > 0 { 323 | log.Printf("specified env vars for 'go': %s", env) 324 | } 325 | } 326 | return cmdEnv 327 | } 328 | 329 | // returns a list of printable args 330 | func PrintableArgs(args []string) string { 331 | ret := "" 332 | for _, arg := range args { 333 | if len(ret) > 0 { 334 | ret = ret + " " 335 | } 336 | if strings.Contains(arg, " ") { 337 | ret = ret + "\"" + arg + "\"" 338 | } else { 339 | ret = ret + arg 340 | } 341 | } 342 | return ret 343 | } 344 | func RedirectIO(cmd *exec.Cmd) { 345 | RedirectIOTo(cmd, os.Stdin, os.Stdout, os.Stderr) 346 | } 347 | 348 | func RedirectIOTo(cmd *exec.Cmd, myin io.Reader, myout, myerr io.Writer) { 349 | // redirect IO 350 | cmd.Stdout = myout 351 | cmd.Stderr = myerr 352 | cmd.Stdin = myin 353 | //return nil, err 354 | } 355 | 356 | // check if cgoEnabled is required. 357 | //0.2.4 refactored this out 358 | // TODO not needed for go1.1+. Remove this once go1.0 reaches end of life. (when is that?) 359 | func CgoEnabled(goos, arch string) string { 360 | var cgoEnabled string 361 | if goos == runtime.GOOS && arch == runtime.GOARCH { 362 | //note: added conditional in line with Dave Cheney, but this combination is not yet supported. 363 | if runtime.GOOS == platforms.FREEBSD && runtime.GOARCH == platforms.ARM { 364 | cgoEnabled = "0" 365 | } else { 366 | cgoEnabled = "1" 367 | } 368 | } else { 369 | cgoEnabled = "0" 370 | } 371 | return cgoEnabled 372 | } 373 | -------------------------------------------------------------------------------- /executils/exec_test.go: -------------------------------------------------------------------------------- 1 | package executils 2 | 3 | /* 4 | func TestGetLdFlagVersionArgs(t *testing.T) { 5 | actual := GetLdFlagVersionArgs("1.1") 6 | expected0 := "-ldflags" 7 | if len(actual) != 2 { 8 | t.Fatalf("unexpected result length != 2 (%v)", actual) 9 | } 10 | if actual[0] != expected0 { 11 | t.Fatalf("unexpected result length != 2 (%v)", actual[0]) 12 | } 13 | } 14 | 15 | func TestGetInterpolationLdFlags(t *testing.T) { 16 | v := map[string]string{"main.VERSION": "1.0", "main.BUILD_DATE": "1-1-1970"} 17 | actual := GetInterpolationLdFlags(v) 18 | expected := []string{"-ldflags", "-X main.VERSION '1.0' -X main.BUILD_DATE '1-1-1970' "} 19 | 20 | if !typeutils.StringSliceEquals(actual, expected) { 21 | t.Fatalf("unexpected result %v != %v", actual, expected) 22 | } 23 | } 24 | */ 25 | -------------------------------------------------------------------------------- /exefileparse/parseexe.go: -------------------------------------------------------------------------------- 1 | package exefileparse 2 | 3 | import ( 4 | "debug/elf" 5 | "debug/macho" 6 | "debug/pe" 7 | "errors" 8 | "log" 9 | "os" 10 | 11 | "github.com/laher/goxc/platforms" 12 | ) 13 | 14 | //I think plan9 uses a plain old a.out file format 15 | var ( 16 | MAGIC_PLAN9_386 = []byte{0, 0, 1, 235} 17 | ) 18 | 19 | func Test(filename, expectedArch, expectedOs string, isVerbose bool) error { 20 | switch expectedOs { 21 | case platforms.WINDOWS: 22 | return TestPE(filename, expectedArch, expectedOs, isVerbose) 23 | case platforms.DARWIN: 24 | return TestMachO(filename, expectedArch, expectedOs, isVerbose) 25 | case platforms.PLAN9: 26 | return TestPlan9Exe(filename, expectedArch, expectedOs, isVerbose) 27 | default: 28 | return TestElf(filename, expectedArch, expectedOs, isVerbose) 29 | } 30 | 31 | } 32 | 33 | func TestElf(filename, expectedArch, expectedOs string, isVerbose bool) error { 34 | file, err := elf.Open(filename) 35 | 36 | if err != nil { 37 | log.Printf("File '%s' is not an ELF file: %v\n", filename, err) 38 | return err 39 | } 40 | defer file.Close() 41 | if isVerbose { 42 | log.Printf("File '%s' is an ELF file (arch: %s, osabi: %s)\n", filename, file.FileHeader.Machine.String(), file.FileHeader.OSABI.String()) 43 | } 44 | if expectedOs == platforms.LINUX { 45 | if file.FileHeader.OSABI != elf.ELFOSABI_NONE && file.FileHeader.OSABI != elf.ELFOSABI_LINUX { 46 | return errors.New("Not a Linux executable") 47 | } 48 | } 49 | if expectedOs == platforms.NETBSD { 50 | if file.FileHeader.OSABI != elf.ELFOSABI_NETBSD { 51 | return errors.New("Not a NetBSD executable") 52 | } 53 | } 54 | if expectedOs == platforms.FREEBSD { 55 | if file.FileHeader.OSABI != elf.ELFOSABI_FREEBSD { 56 | return errors.New("Not a FreeBSD executable") 57 | } 58 | } 59 | if expectedOs == platforms.OPENBSD { 60 | if file.FileHeader.OSABI != elf.ELFOSABI_OPENBSD { 61 | return errors.New("Not an OpenBSD executable") 62 | } 63 | } 64 | 65 | if expectedArch == platforms.ARM { 66 | if file.FileHeader.Machine != elf.EM_ARM { 67 | return errors.New("Not an ARM executable") 68 | } 69 | } 70 | if expectedArch == platforms.X86 { 71 | if file.FileHeader.Machine != elf.EM_386 { 72 | return errors.New("Not a 386 executable") 73 | } 74 | 75 | } 76 | if expectedArch == platforms.AMD64 { 77 | if file.FileHeader.Machine != elf.EM_X86_64 { 78 | return errors.New("Not an AMD64 executable") 79 | } 80 | 81 | } 82 | return nil 83 | } 84 | 85 | func TestMachO(filename, expectedArch, expectedOs string, isVerbose bool) error { 86 | file, err := macho.Open(filename) 87 | if err != nil { 88 | 89 | log.Printf("File '%s' is not a Mach-O file: %v\n", filename, err) 90 | return err 91 | } 92 | defer file.Close() 93 | if isVerbose { 94 | log.Printf("File '%s' is a Mach-O file (arch: %s)\n", filename, file.FileHeader.Cpu.String()) 95 | } 96 | if expectedArch == platforms.X86 { 97 | if file.FileHeader.Cpu != macho.Cpu386 { 98 | return errors.New("Not a 386 executable") 99 | } 100 | 101 | } 102 | if expectedArch == platforms.AMD64 { 103 | if file.FileHeader.Cpu != macho.CpuAmd64 { 104 | return errors.New("Not an AMD64 executable") 105 | } 106 | 107 | } 108 | return nil 109 | } 110 | 111 | func TestPE(filename, expectedArch, expectedOs string, isVerbose bool) error { 112 | file, err := pe.Open(filename) 113 | if err != nil { 114 | return errors.New("NOT a PE file") 115 | } 116 | defer file.Close() 117 | if isVerbose { 118 | log.Printf("File '%s' is a PE file, arch: %d (%d='X86' and %d='AMD64')\n", filename, file.FileHeader.Machine, pe.IMAGE_FILE_MACHINE_I386, pe.IMAGE_FILE_MACHINE_AMD64) 119 | } 120 | if expectedArch == platforms.X86 { 121 | if file.FileHeader.Machine != pe.IMAGE_FILE_MACHINE_I386 { 122 | return errors.New("Not a 386 executable") 123 | } 124 | 125 | } 126 | if expectedArch == platforms.AMD64 { 127 | if file.FileHeader.Machine != pe.IMAGE_FILE_MACHINE_AMD64 { 128 | return errors.New("Not an AMD64 executable") 129 | } 130 | 131 | } 132 | return nil 133 | 134 | } 135 | 136 | func TestPlan9Exe(filename, expectedArch, expectedOs string, isVerbose bool) error { 137 | file, err := os.Open(filename) 138 | if err != nil { 139 | return errors.New("Could not open file") 140 | } 141 | defer file.Close() 142 | b := []byte{0, 0, 0, 0} 143 | i, err := file.Read(b) 144 | if err != nil || i < 4 { 145 | return errors.New("Could not read first 2 bytes of file") 146 | } 147 | 148 | if expectedArch == platforms.X86 { 149 | for i := range b { 150 | if b[i] != MAGIC_PLAN9_386[i] { 151 | return errors.New("NOT a known Plan9 executable format") 152 | } 153 | } 154 | if isVerbose { 155 | log.Printf("File '%s' is a Plan9 executable", filename) 156 | } 157 | } 158 | return nil 159 | 160 | } 161 | -------------------------------------------------------------------------------- /goxc_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/laher/goxc/config" 8 | ) 9 | 10 | func TestRemove(t *testing.T) { 11 | //goroot := runtime.GOROOT() 12 | arr := []string{"1", "2"} 13 | removed := remove(arr, "1") 14 | if len(removed) != 1 { 15 | t.Fatalf("Remove failed!") 16 | } 17 | removed = remove(arr, "3") 18 | if len(removed) != 2 { 19 | t.Fatalf("Remove failed!") 20 | } 21 | } 22 | 23 | func TestPrintJsonDefaults(t *testing.T) { 24 | settings := config.Settings{} 25 | config.FillSettingsDefaults(&settings, ".") 26 | jsonD, _ := json.MarshalIndent(settings, "", "\t") 27 | t.Logf("JSON defaults: \n%+v", string(jsonD)) 28 | } 29 | -------------------------------------------------------------------------------- /htmltemplate.goxc.json: -------------------------------------------------------------------------------- 1 | { 2 | "FormatVersion": "0.8", 3 | "TaskSettings": { 4 | "downloads-page" : { 5 | "filename" : "downloads.htm", 6 | "templateFile" : "downloads.htpl" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /notdarwin.goxc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ArtifactsDest": "", 3 | "BuildConstraints": "!darwin", 4 | "Resources": {}, 5 | "FormatVersion": "0.8" 6 | } 7 | -------------------------------------------------------------------------------- /packaging/sdeb/constants.go: -------------------------------------------------------------------------------- 1 | package sdeb 2 | 3 | const ( 4 | TEMPLATE_DEBIAN_COMPAT = "9" 5 | FORMAT_DEFAULT = "3.0 (quilt)" 6 | TEMPLATE_DEBIAN_SOURCE_FORMAT = FORMAT_DEFAULT 7 | TEMPLATE_DEBIAN_SOURCE_OPTIONS = `tar-ignore = .hg 8 | tar-ignore = .git 9 | tar-ignore = .bzr` 10 | 11 | TEMPLATE_DEBIAN_RULES = `#!/usr/bin/make -f 12 | # -*- makefile -*- 13 | 14 | # Uncomment this to turn on verbose mode. 15 | #export DH_VERBOSE=1 16 | 17 | export GOPATH=$(CURDIR) 18 | 19 | PKGDIR=debian/{{.PackageName}} 20 | 21 | %: 22 | dh $@ 23 | 24 | clean: 25 | dh_clean 26 | rm -rf $(GOPATH)/bin/* $(GOPATH)/pkg/* 27 | #cd $(GOPATH)/src && find * -name '*.go' -exec dirname {} \; | xargs -n1 go clean 28 | rm -f $(GOPATH)/goinstall.log 29 | 30 | binary-arch: clean 31 | dh_prep 32 | dh_installdirs 33 | cd $(GOPATH)/src && find * -name '*.go' -exec dirname {} \; | xargs -n1 go install 34 | mkdir -p $(PKGDIR)/usr/bin 35 | cp $(GOPATH)/bin/* $(PKGDIR)/usr/bin/ 36 | dh_strip 37 | dh_compress 38 | dh_fixperms 39 | dh_installdeb 40 | dh_gencontrol 41 | dh_md5sums 42 | dh_builddeb 43 | 44 | binary: binary-arch` 45 | 46 | TEMPLATE_SOURCEDEB_CONTROL = `Source: {{.PackageName}} 47 | Build-Depends: {{.BuildDepends}} 48 | Priority: {{.Priority}} 49 | Maintainer: {{.Maintainer}} 50 | Standards-Version: {{.StandardsVersion}} 51 | Section: {{.Section}} 52 | 53 | Package: {{.PackageName}} 54 | Architecture: {{.Architecture}} 55 | Depends: ${misc:Depends}{{.Depends}} 56 | Description: {{.Description}} 57 | {{.Other}}` 58 | 59 | TEMPLATE_DEBIAN_DSC = `Format: {{.Format}} 60 | Source: {{.PackageName}} 61 | Binary: {{.PackageName}} 62 | Architecture: {{.Architecture}} 63 | Version: {{.PackageVersion}} 64 | Maintainer: {{.Maintainer}} 65 | Standards-Version: {{.StandardsVersion}} 66 | Build-Depends: {{.BuildDepends}} 67 | Priority: {{.Priority}} 68 | Section: {{.Section}} 69 | Checksums-Sha1:{{range .ChecksumsSha1}} 70 | {{.Checksum}} {{.Size}} {{.File}}{{end}} 71 | Checksums-Sha256:{{range .ChecksumsSha256}} 72 | {{.Checksum}} {{.Size}} {{.File}}{{end}} 73 | Files:{{range .Files}} 74 | {{.Checksum}} {{.Size}} {{.File}}{{end}} 75 | {{.Other}}` 76 | 77 | TEMPLATE_CHANGELOG_HEADER = `{{.PackageName}} ({{.PackageVersion}}) {{.Status}}; urgency=low` 78 | TEMPLATE_CHANGELOG_INITIAL_ENTRY = ` * Initial import` 79 | TEMPLATE_CHANGELOG_FOOTER = ` -- {{.Maintainer}} <{{.MaintainerEmail}}> {{.EntryDate}}` 80 | TEMPLATE_DEBIAN_COPYRIGHT = `Copyright 2013 {{.PackageName}}` 81 | TEMPLATE_DEBIAN_README = `{{.PackageName}} 82 | ========== 83 | 84 | ` 85 | 86 | STATUS_DEFAULT = "unreleased" 87 | SECTION_DEFAULT = "devel" //TODO: correct to use this? 88 | PRIORITY_DEFAULT = "extra" 89 | BUILD_DEPENDS_DEFAULT = "debhelper (>= 9.1.0), golang-go" 90 | STANDARDS_VERSION_DEFAULT = "3.9.4" 91 | ARCHITECTURE_DEFAULT = "any" 92 | DIRNAME_TEMP = ".goxc-temp" 93 | ) 94 | -------------------------------------------------------------------------------- /packaging/sdeb/sdeb.go: -------------------------------------------------------------------------------- 1 | package sdeb 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "io" 23 | "io/ioutil" 24 | "log" 25 | "os" 26 | "path/filepath" 27 | 28 | "github.com/laher/goxc/archive" 29 | "github.com/laher/goxc/core" 30 | //"text/template" 31 | ) 32 | 33 | //TODO: unfinished: need to discover root dir to determine which dirs to pre-make. 34 | func SdebGetSourcesAsArchiveItems(codeDir, prefix string) ([]archive.ArchiveItem, error) { 35 | goPathRoot := core.GetGoPathElement(codeDir) 36 | goPathRootResolved, err := filepath.EvalSymlinks(goPathRoot) 37 | if err != nil { 38 | log.Printf("Could not evaluate symlinks for %s", goPathRoot) 39 | goPathRootResolved = goPathRoot 40 | } 41 | log.Printf("Code dir %s (using goPath element %s)", codeDir, goPathRootResolved) 42 | return sdebGetSourcesAsArchiveItems(goPathRootResolved, codeDir, prefix) 43 | } 44 | 45 | // 46 | func sdebGetSourcesAsArchiveItems(goPathRoot, codeDir, prefix string) ([]archive.ArchiveItem, error) { 47 | sources := []archive.ArchiveItem{} 48 | //1. Glob for files in this dir 49 | //log.Printf("Globbing %s", codeDir) 50 | matches, err := filepath.Glob(filepath.Join(codeDir, "*.go")) 51 | if err != nil { 52 | return sources, err 53 | } 54 | for _, match := range matches { 55 | relativeMatch, err := filepath.Rel(goPathRoot, match) 56 | if err != nil { 57 | return nil, errors.New("Error finding go sources " + err.Error()) 58 | } 59 | destName := filepath.Join(prefix, relativeMatch) 60 | //log.Printf("Putting file %s in %s", match, destName) 61 | sources = append(sources, archive.ArchiveItemFromFileSystem(match, destName)) 62 | } 63 | 64 | //2. Recurse into subdirs 65 | fis, err := ioutil.ReadDir(codeDir) 66 | for _, fi := range fis { 67 | if fi.IsDir() && fi.Name() != DIRNAME_TEMP { 68 | additionalItems, err := sdebGetSourcesAsArchiveItems(goPathRoot, filepath.Join(codeDir, fi.Name()), prefix) 69 | sources = append(sources, additionalItems...) 70 | if err != nil { 71 | return sources, err 72 | } 73 | } 74 | } 75 | return sources, err 76 | } 77 | 78 | func SdebCopySourceRecurse(codeDir, destDir string) (err error) { 79 | log.Printf("Globbing %s", codeDir) 80 | //get all files and copy into destDir 81 | matches, err := filepath.Glob(filepath.Join(codeDir, "*.go")) 82 | if err != nil { 83 | return err 84 | } 85 | if len(matches) > 0 { 86 | err = os.MkdirAll(destDir, 0777) 87 | if err != nil { 88 | return err 89 | } 90 | } 91 | for _, match := range matches { 92 | //TODO copy files 93 | log.Printf("copying %s into %s", match, filepath.Join(destDir, filepath.Base(match))) 94 | r, err := os.Open(match) 95 | if err != nil { 96 | return err 97 | } 98 | defer func() { 99 | err := r.Close() 100 | if err != nil { 101 | panic(err) 102 | } 103 | }() 104 | w, err := os.Create(filepath.Join(destDir, filepath.Base(match))) 105 | if err != nil { 106 | return err 107 | } 108 | defer func() { 109 | err := w.Close() 110 | if err != nil { 111 | panic(err) 112 | } 113 | }() 114 | 115 | _, err = io.Copy(w, r) 116 | if err != nil { 117 | return err 118 | } 119 | } 120 | fis, err := ioutil.ReadDir(codeDir) 121 | for _, fi := range fis { 122 | if fi.IsDir() && fi.Name() != DIRNAME_TEMP { 123 | err = SdebCopySourceRecurse(filepath.Join(codeDir, fi.Name()), filepath.Join(destDir, fi.Name())) 124 | if err != nil { 125 | return err 126 | } 127 | } 128 | } 129 | return nil 130 | } 131 | 132 | /* 133 | // prepare folders and debian/ files. 134 | // (everything except copying source) 135 | func SdebPrepare(workingDirectory, appName, maintainer, version, arches, description, buildDepends string, metadataDeb map[string]interface{}) (err error) { 136 | //make temp dir & subfolders 137 | tmpDir := filepath.Join(workingDirectory, DIRNAME_TEMP) 138 | debianDir := filepath.Join(tmpDir, "debian") 139 | err = os.MkdirAll(filepath.Join(debianDir, "source"), 0777) 140 | if err != nil { 141 | return err 142 | } 143 | err = os.MkdirAll(filepath.Join(tmpDir, "src"), 0777) 144 | if err != nil { 145 | return err 146 | } 147 | err = os.MkdirAll(filepath.Join(tmpDir, "bin"), 0777) 148 | if err != nil { 149 | return err 150 | } 151 | err = os.MkdirAll(filepath.Join(tmpDir, "pkg"), 0777) 152 | if err != nil { 153 | return err 154 | } 155 | //write control file and related files 156 | tpl, err := template.New("rules").Parse(TEMPLATE_DEBIAN_RULES) 157 | if err != nil { 158 | return err 159 | } 160 | file, err := os.Create(filepath.Join(debianDir, "rules")) 161 | if err != nil { 162 | return err 163 | } 164 | defer func() { 165 | err := file.Close() 166 | if err != nil { 167 | panic(err) 168 | } 169 | }() 170 | err = tpl.Execute(file, appName) 171 | if err != nil { 172 | return err 173 | } 174 | sdebControlFile := getSdebControlFileContent(appName, maintainer, version, arches, description, buildDepends, metadataDeb) 175 | ioutil.WriteFile(filepath.Join(debianDir, "control"), sdebControlFile, 0666) 176 | //copy source into folders 177 | //call dpkg-build, if available 178 | return err 179 | } 180 | */ 181 | func getSdebControlFileContent(appName, maintainer, version, arches, description, buildDepends string, metadataDeb map[string]interface{}) []byte { 182 | control := fmt.Sprintf("Source: %s\nPriority: extra\n", appName) 183 | if maintainer != "" { 184 | control = fmt.Sprintf("%sMaintainer: %s\n", control, maintainer) 185 | } 186 | if buildDepends == "" { 187 | buildDepends = BUILD_DEPENDS_DEFAULT 188 | } 189 | control = fmt.Sprintf("%sBuildDepends: %s\n", control, buildDepends) 190 | control = fmt.Sprintf("%sStandards-Version: %s\n", control, STANDARDS_VERSION_DEFAULT) 191 | 192 | //TODO - homepage? 193 | 194 | control = fmt.Sprintf("%sVersion: %s\n", control, version) 195 | 196 | control = fmt.Sprintf("%sPackage: %s\n", control, appName) 197 | 198 | //mandatory 199 | control = fmt.Sprintf("%sArchitecture: %s\n", control, arches) 200 | for k, v := range metadataDeb { 201 | control = fmt.Sprintf("%s%s: %s\n", control, k, v) 202 | } 203 | control = fmt.Sprintf("%sDescription: %s\n", control, description) 204 | return []byte(control) 205 | } 206 | -------------------------------------------------------------------------------- /packaging/sdeb/sdeb_test.go: -------------------------------------------------------------------------------- 1 | package sdeb 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | ) 8 | 9 | func TestSdebBuild(t *testing.T) { 10 | workingDirectory := "." 11 | err := os.MkdirAll(workingDirectory, 0777) 12 | if err != nil { 13 | t.Fatalf("%v", err) 14 | } 15 | /* SdebPrepare is old now 16 | err = SdebPrepare(workingDirectory, "my-app-x", "A L", "1.2.3-alpha", "platform-x", "This package does x", "", *new(map[string]interface{})) 17 | if err != nil { 18 | t.Fatalf("%v", err) 19 | } 20 | */ 21 | tmpDir := filepath.Join(workingDirectory, DIRNAME_TEMP) 22 | destDir := filepath.Join(tmpDir, "src") 23 | workingDirectory = "../.." 24 | err = SdebCopySourceRecurse(workingDirectory, destDir) 25 | if err != nil { 26 | t.Fatalf("%v", err) 27 | } 28 | //TODO: find code & copy 29 | //ioutil.WriteFile(filepath.Join(debianDir, "control"), sdebControlFile, 0666) 30 | //TODO: targz 31 | } 32 | -------------------------------------------------------------------------------- /platforms/buildconstraints.go: -------------------------------------------------------------------------------- 1 | package platforms 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "log" 21 | "strings" 22 | 23 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 24 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 25 | "github.com/laher/goxc/typeutils" 26 | ) 27 | 28 | // parse and filter list of platforms 29 | func ApplyBuildConstraints(buildConstraints string, unfilteredPlatforms []Platform) []Platform { 30 | ret := []Platform{} 31 | items := strings.FieldsFunc(buildConstraints, func(r rune) bool { return r == ' ' }) 32 | if len(items) == 0 { 33 | return unfilteredPlatforms 34 | } 35 | for _, item := range items { 36 | parts := strings.FieldsFunc(item, func(r rune) bool { return r == ',' }) 37 | itemOs := []string{} 38 | itemNegOs := []string{} 39 | itemArch := []string{} 40 | itemNegArch := []string{} 41 | for _, part := range parts { 42 | isNeg, modulus := isNegative(part) 43 | if IsOs(modulus) { 44 | if isNeg { 45 | itemNegOs = append(itemNegOs, modulus) 46 | } else { 47 | itemOs = append(itemOs, modulus) 48 | } 49 | } else if IsArch(modulus) { 50 | if isNeg { 51 | itemNegArch = append(itemNegArch, modulus) 52 | } else { 53 | itemArch = append(itemArch, modulus) 54 | } 55 | 56 | } else { 57 | log.Printf("Unrecognised build constraint! Ignoring '%s'", part) 58 | } 59 | } 60 | ret = append(ret, resolveItem(itemOs, itemNegOs, itemArch, itemNegArch, unfilteredPlatforms)...) 61 | } 62 | return ret 63 | } 64 | 65 | // check if a string is a valid architecture name 66 | func IsArch(part string) bool { 67 | return typeutils.StringSlicePos(ARCHS, part) > -1 68 | } 69 | 70 | // check if a string is a valid OS name 71 | func IsOs(part string) bool { 72 | return typeutils.StringSlicePos(OSES, part) > -1 73 | } 74 | 75 | func isNegative(part string) (bool, string) { 76 | isNeg := strings.HasPrefix(part, "!") 77 | if isNeg { 78 | return true, part[1:] 79 | } 80 | return false, part 81 | } 82 | 83 | func resolveItem(itemOses, itemNegOses, itemArchs, itemNegArchs []string, unfilteredPlatforms []Platform) []Platform { 84 | ret := []Platform{} 85 | if len(itemOses) == 0 { 86 | //none specified: add all 87 | itemOses = getOses(unfilteredPlatforms) 88 | } 89 | for _, itemNegOs := range itemNegOses { 90 | //log.Printf("negos " + itemNegOs) 91 | itemOses = typeutils.StringSliceDelAll(itemOses, itemNegOs) 92 | } 93 | 94 | //log.Printf("oses %v", itemOses) 95 | for _, itemOs := range itemOses { 96 | itemArchsThisOs := make([]string, len(itemArchs)) 97 | copy(itemArchsThisOs, itemArchs) 98 | if len(itemArchs) == 0 { 99 | //none specified: add all 100 | itemArchsThisOs = getArchsForOs(unfilteredPlatforms, itemOs) 101 | } 102 | for _, itemNegArch := range itemNegArchs { 103 | itemArchsThisOs = typeutils.StringSliceDelAll(itemArchsThisOs, itemNegArch) 104 | } 105 | for _, itemArch := range itemArchsThisOs { 106 | ret = append(ret, Platform{itemOs, itemArch}) 107 | } 108 | } 109 | return ret 110 | } 111 | 112 | func getArchsForOs(sp []Platform, os string) []string { 113 | archs := []string{} 114 | for _, p := range sp { 115 | if p.Os == os { 116 | archs = append(archs, p.Arch) 117 | } 118 | } 119 | return archs 120 | } 121 | func getOses(sp []Platform) []string { 122 | oses := []string{} 123 | for _, p := range sp { 124 | if !typeutils.StringSliceContains(oses, p.Os) { 125 | oses = append(oses, p.Os) 126 | } 127 | } 128 | return oses 129 | } 130 | -------------------------------------------------------------------------------- /platforms/buildconstraints_test.go: -------------------------------------------------------------------------------- 1 | package platforms 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func Test1(t *testing.T) { 9 | testBCs := map[string]string{ 10 | "freebsd linux,!arm": "[{freebsd 386} {freebsd amd64} {linux 386} {linux amd64}]", 11 | "windows": "[{windows 386} {windows amd64}]", 12 | "windows,!386": "[{windows amd64}]", 13 | "!windows": "[{darwin 386} {darwin amd64} {linux 386} {linux amd64} {linux arm} {freebsd 386} {freebsd amd64} {openbsd 386} {openbsd amd64}]", 14 | "!386": "[{darwin amd64} {linux amd64} {linux arm} {freebsd amd64} {openbsd amd64} {windows amd64}]", 15 | "": "[{darwin 386} {darwin amd64} {linux 386} {linux amd64} {linux arm} {freebsd 386} {freebsd amd64} {openbsd 386} {openbsd amd64} {windows 386} {windows amd64}]", 16 | } 17 | for buildConstraints, expectedPlatforms := range testBCs { 18 | targets := ApplyBuildConstraints(buildConstraints, SUPPORTED_PLATFORMS_1_0) 19 | t.Logf("build: %s. targets: %v", buildConstraints, targets) 20 | targetsAsString := fmt.Sprintf("%v", targets) 21 | if targetsAsString != expectedPlatforms { 22 | t.Fatalf("unexpected result %v != %v", expectedPlatforms, targets) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /platforms/platforms.go: -------------------------------------------------------------------------------- 1 | // Support for different target platforms (Operating Systems and Architectures) supported by the Go compiler 2 | package platforms 3 | 4 | /* 5 | Copyright 2013 Am Laher 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | import ( 21 | "log" 22 | "runtime" 23 | "strings" 24 | ) 25 | 26 | const ( 27 | AMD64 = "amd64" 28 | AMD64P32 = "amd64p32" 29 | X86 = "386" 30 | ARM = "arm" 31 | ARM64 = "arm64" 32 | 33 | DARWIN = "darwin" 34 | DRAGONFLY = "dragonfly" 35 | FREEBSD = "freebsd" 36 | LINUX = "linux" 37 | NACL = "nacl" 38 | NETBSD = "netbsd" 39 | OPENBSD = "openbsd" 40 | PLAN9 = "plan9" 41 | SOLARIS = "solaris" 42 | WINDOWS = "windows" 43 | ) 44 | 45 | // represents a target compilation platform 46 | type Platform struct { 47 | Os string 48 | Arch string 49 | } 50 | 51 | var ( 52 | OSES = []string{DARWIN, LINUX, FREEBSD, NETBSD, OPENBSD, PLAN9, WINDOWS, SOLARIS, DRAGONFLY, NACL} 53 | ARCHS = []string{X86, AMD64, ARM, ARM64} 54 | SUPPORTED_PLATFORMS_1_0 = []Platform{ 55 | Platform{DARWIN, X86}, 56 | Platform{DARWIN, AMD64}, 57 | Platform{LINUX, X86}, 58 | Platform{LINUX, AMD64}, 59 | Platform{LINUX, ARM}, 60 | Platform{FREEBSD, X86}, 61 | Platform{FREEBSD, AMD64}, 62 | Platform{OPENBSD, X86}, 63 | Platform{OPENBSD, AMD64}, 64 | Platform{WINDOWS, X86}, 65 | Platform{WINDOWS, AMD64}} 66 | NEW_PLATFORMS_1_1 = []Platform{ 67 | Platform{FREEBSD, ARM}, 68 | Platform{NETBSD, X86}, 69 | Platform{NETBSD, AMD64}, 70 | Platform{NETBSD, ARM}, 71 | Platform{PLAN9, X86}} 72 | NEW_PLATFORMS_1_3 = []Platform{ 73 | // Platform{DRAGONFLY, X86}, <-- no longer supported even by dragonfly 74 | Platform{DRAGONFLY, AMD64}, 75 | Platform{NACL, X86}, 76 | Platform{NACL, AMD64P32}, 77 | Platform{SOLARIS, AMD64}} 78 | NEW_PLATFORMS_1_4 = []Platform{ 79 | Platform{NACL, ARM}, 80 | } 81 | NEW_PLATFORMS_1_5 = []Platform{ 82 | Platform{PLAN9, AMD64}, 83 | // Platform{DARWIN, ARM}, <-- requires admin rights and special IOS stuffs. Same for DARWIN/ARM64 84 | } 85 | 86 | SUPPORTED_PLATFORMS_1_1 = append(append([]Platform{}, SUPPORTED_PLATFORMS_1_0...), NEW_PLATFORMS_1_1...) 87 | SUPPORTED_PLATFORMS_1_3 = append(append([]Platform{}, SUPPORTED_PLATFORMS_1_1...), NEW_PLATFORMS_1_3...) 88 | SUPPORTED_PLATFORMS_1_4 = append(append([]Platform{}, SUPPORTED_PLATFORMS_1_3...), NEW_PLATFORMS_1_4...) 89 | SUPPORTED_PLATFORMS_1_5 = append(append([]Platform{}, SUPPORTED_PLATFORMS_1_4...), NEW_PLATFORMS_1_5...) 90 | ) 91 | 92 | func getSupportedPlatforms() []Platform { 93 | if strings.HasPrefix(runtime.Version(), "go1.5") { 94 | return SUPPORTED_PLATFORMS_1_5 95 | } 96 | if strings.HasPrefix(runtime.Version(), "go1.4") { 97 | return SUPPORTED_PLATFORMS_1_4 98 | } 99 | if strings.HasPrefix(runtime.Version(), "go1.3") { 100 | return SUPPORTED_PLATFORMS_1_3 101 | } 102 | // otherwise default to <= go1.2 103 | return SUPPORTED_PLATFORMS_1_1 104 | } 105 | 106 | func ContainsPlatform(haystack []Platform, needle Platform) bool { 107 | for _, p := range haystack { 108 | if p.Os == needle.Os && p.Arch == needle.Arch { 109 | return true 110 | } 111 | } 112 | return false 113 | } 114 | 115 | // interpret list of destination platforms (based on os & arch settings) 116 | //0.5 add support for space delimiters (similar to BuildConstraints) 117 | //0.5 add support for different oses/services 118 | func GetDestPlatforms(specifiedOses string, specifiedArches string) []Platform { 119 | destOses := strings.FieldsFunc(specifiedOses, func(r rune) bool { return r == ',' || r == ' ' }) 120 | destArchs := strings.FieldsFunc(specifiedArches, func(r rune) bool { return r == ',' || r == ' ' }) 121 | 122 | for _, o := range destOses { 123 | supported := false 124 | for _, supportedPlatformArr := range getSupportedPlatforms() { 125 | supportedOs := supportedPlatformArr.Os 126 | if o == supportedOs { 127 | supported = true 128 | } 129 | } 130 | if !supported { 131 | log.Printf("WARNING: Operating System '%s' is unsupported", o) 132 | } 133 | } 134 | for _, o := range destArchs { 135 | supported := false 136 | for _, supportedPlatformArr := range getSupportedPlatforms() { 137 | supportedArch := supportedPlatformArr.Arch 138 | if o == supportedArch { 139 | supported = true 140 | } 141 | } 142 | if !supported { 143 | log.Printf("WARNING: Architecture '%s' is unsupported", o) 144 | } 145 | } 146 | if len(destOses) == 0 { 147 | destOses = []string{""} 148 | } 149 | if len(destArchs) == 0 { 150 | destArchs = []string{""} 151 | } 152 | var destPlatforms []Platform 153 | for _, supportedPlatformArr := range getSupportedPlatforms() { 154 | supportedOs := supportedPlatformArr.Os 155 | supportedArch := supportedPlatformArr.Arch 156 | for _, destOs := range destOses { 157 | if destOs == "" || supportedOs == destOs { 158 | for _, destArch := range destArchs { 159 | if destArch == "" || supportedArch == destArch { 160 | destPlatforms = append(destPlatforms, supportedPlatformArr) 161 | } 162 | } 163 | } 164 | } 165 | } 166 | if len(destPlatforms) < 1 { 167 | log.Printf("WARNING: no valid platforms specified") 168 | } 169 | return destPlatforms 170 | } 171 | -------------------------------------------------------------------------------- /sample.goxc.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "PrereleaseInfo" : "alpha", 3 | "BranchName" : "mybranch", 4 | "TaskSettings": { 5 | "bintray": { 6 | "apihost": "http://localhost:2020/", 7 | "apikey": "12d312314235afe56090932ea13234" 8 | }, 9 | "publish-github": { 10 | "apihost": "http://localhost:2020/", 11 | "apikey": "12d312314235afe56090932ea13234" 12 | } 13 | 14 | }, 15 | "FormatVersion": "0.8" 16 | } 17 | -------------------------------------------------------------------------------- /source/parser.go: -------------------------------------------------------------------------------- 1 | // Limited support for parsing Go source. 2 | package source 3 | 4 | /* 5 | Copyright 2013 Am Laher 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | import ( 21 | "go/ast" 22 | "go/parser" 23 | "go/token" 24 | "log" 25 | "os" 26 | "path/filepath" 27 | "strings" 28 | ) 29 | 30 | func FindMainDirs(root string, excludingGlobs []string, isVerbose bool) ([]string, error) { 31 | return FindSourceDirs(root, "main", excludingGlobs, isVerbose) 32 | } 33 | 34 | func FindSourceDirs(root string, packageNameFilter string, excludingGlobs []string, isVerbose bool) ([]string, error) { 35 | mainDirs := []string{} 36 | sourceFiles := []string{} 37 | root, err := filepath.Abs(root) 38 | if err != nil { 39 | log.Printf("Error resolving root dir: %v", err) 40 | return []string{}, err 41 | } 42 | root, err = filepath.EvalSymlinks(root) 43 | if err != nil { 44 | log.Printf("Error resolving root dir (EvalSymlinks): %v", err) 45 | return []string{}, err 46 | } 47 | err = filepath.Walk(root, func(path string, info os.FileInfo, inerr error) error { 48 | 49 | var err error 50 | //find files ending with .go 51 | //check for 'package main' 52 | //if strings.Contains(path, string(filepath.Separator) + ".") { 53 | //skip '.hidden' dirs 54 | if strings.HasPrefix(filepath.Base(path), ".") { 55 | finfo, err := os.Stat(path) 56 | if err != nil { 57 | log.Printf("Error stat'ing %s", path) 58 | return err 59 | } 60 | if finfo.IsDir() { 61 | return filepath.SkipDir 62 | } else { 63 | //only log if it's a go file 64 | if strings.HasSuffix(path, ".go") && isVerbose { 65 | log.Printf("Ignoring '.hidden' file %s", path) 66 | } 67 | } 68 | } else if strings.HasSuffix(path, ".go") { 69 | //read file and check package 70 | sourceFiles = append(sourceFiles, path) 71 | } 72 | return err 73 | }) 74 | if err != nil { 75 | log.Printf("Error: %v", err) 76 | return mainDirs, err 77 | } 78 | parsedMap, err := LoadFilesMap(sourceFiles) 79 | if err != nil { 80 | log.Printf("Error: %v", err) 81 | return mainDirs, err 82 | } 83 | for name, file := range parsedMap { 84 | if packageNameFilter == "" || file.Name.Name == packageNameFilter { 85 | mainDir, err := filepath.Abs(filepath.Dir(name)) 86 | if err != nil { 87 | log.Printf("Abs error: %s: %v", filepath.Dir(name), err) 88 | } else { 89 | excluded := false 90 | for _, exclGlob := range excludingGlobs { 91 | //log.Printf("Glob testing: %s matches %s", filepath.Join(root, exclGlob), mainDir) 92 | matches, err := filepath.Match(filepath.Join(root, exclGlob), mainDir) 93 | if err != nil { 94 | //ignore this exclusion glob 95 | log.Printf("Glob error: %s: %s", exclGlob, err) 96 | } else if matches { 97 | if isVerbose { 98 | log.Printf("Main dir '%s' excluded by glob '%s'", mainDir, exclGlob) 99 | } 100 | excluded = true 101 | } else { 102 | absExcl, err := filepath.Abs(filepath.Join(root, exclGlob)) 103 | if err != nil { 104 | //ignore 105 | log.Printf("Abs error: %s: %v", filepath.Join(root, exclGlob), err) 106 | } else if strings.HasPrefix(mainDir, absExcl) { 107 | if isVerbose { 108 | log.Printf("Main dir '%s' excluded because it is in '%s'", mainDir, absExcl) 109 | } 110 | excluded = true 111 | } else { 112 | //log.Printf("Main dir '%s' is NOT in '%s'", mainDir, absExcl) 113 | } 114 | } 115 | 116 | } 117 | if !excluded { 118 | alreadyThere := false 119 | for _, v := range mainDirs { 120 | if v == mainDir { 121 | alreadyThere = true 122 | } 123 | } 124 | if !alreadyThere { 125 | mainDirs = append(mainDirs, mainDir) 126 | } 127 | } 128 | } 129 | } 130 | } 131 | return mainDirs, err 132 | } 133 | 134 | func LoadFilesMap(filenames []string) (map[string]*ast.File, error) { 135 | filesMap := map[string]*ast.File{} 136 | fset := token.NewFileSet() // positions are relative to fset 137 | for _, match := range filenames { 138 | f, err := parser.ParseFile(fset, match, nil, 0) 139 | if err != nil { 140 | log.Printf("Source parser error %v", err) 141 | } else { 142 | filesMap[match] = f 143 | } 144 | } 145 | return filesMap, nil 146 | } 147 | func LoadFiles(filenames []string) ([]*ast.File, error) { 148 | files := []*ast.File{} 149 | fset := token.NewFileSet() // positions are relative to fset 150 | for _, match := range filenames { 151 | f, err := parser.ParseFile(fset, match, nil, 0) 152 | if err != nil { 153 | log.Printf("Source parser error %v", err) 154 | } else { 155 | files = append(files, f) 156 | } 157 | } 158 | return files, nil 159 | } 160 | 161 | func FindConstantValue(f *ast.File, name string, isVerbose bool) *ast.BasicLit { 162 | return FindValue(f, name, []token.Token{token.CONST}, isVerbose) 163 | } 164 | 165 | //TODO: refactor to more idiomatic version of this (e.g. use Visit?) 166 | func FindValue(f *ast.File, name string, toks []token.Token, isVerbose bool) *ast.BasicLit { 167 | isName := false 168 | isTok := false 169 | value := "" 170 | var ret *ast.BasicLit 171 | ast.Inspect(f, func(n ast.Node) bool { 172 | switch x := n.(type) { 173 | case *ast.GenDecl: 174 | isTok = false 175 | for _, tok := range toks { 176 | if x.Tok == tok { 177 | isTok = true 178 | } 179 | } 180 | case *ast.BasicLit: 181 | if isName && isTok { 182 | //strip quotes 183 | value = strings.Replace(x.Value, "\"", "", -1) 184 | if isVerbose { 185 | log.Printf("Found value (%s)", value) 186 | } 187 | ret = x 188 | isName = false 189 | return false //break out 190 | } 191 | case *ast.Ident: 192 | isName = false 193 | if isTok { 194 | //log.Printf("Matching token type, named %s, %+v", x.Name, x) 195 | if x.Name == name { 196 | isName = true 197 | } 198 | } 199 | } 200 | return true 201 | }) 202 | //return value, found 203 | return ret 204 | } 205 | -------------------------------------------------------------------------------- /source/parser_test.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | ) 7 | 8 | func TestLearning(t *testing.T) { 9 | matches, err := filepath.Glob(filepath.Join("..", "goxc.go")) 10 | if err != nil { 11 | t.Logf("Glob error %v", err) 12 | } else { 13 | files, err := LoadFiles(matches) 14 | if err != nil { 15 | t.Logf("%v", err) 16 | return 17 | } 18 | for _, f := range files { 19 | version := FindConstantValue(f, "PKG_VERSION", true) 20 | t.Logf("Version = %v", version) 21 | name := FindConstantValue(f, "PKG_NAME", true) 22 | t.Logf("Name = %v", name) 23 | } 24 | } 25 | } 26 | 27 | func TestMainDirs(t *testing.T) { 28 | mds, err := FindMainDirs("..", []string{}, true) 29 | if err != nil { 30 | t.Logf("%v", err) 31 | return 32 | } 33 | t.Logf("mainDirs: %s", mds) 34 | } 35 | -------------------------------------------------------------------------------- /tasks/archive.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "errors" 21 | "log" 22 | "os" 23 | "path/filepath" 24 | 25 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 26 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 27 | "github.com/laher/goxc/archive" 28 | "github.com/laher/goxc/config" 29 | "github.com/laher/goxc/core" 30 | "github.com/laher/goxc/platforms" 31 | ) 32 | 33 | //runs automatically 34 | func init() { 35 | RegisterParallelizable(ParallelizableTask{ 36 | TASK_ZIP, 37 | "Create a zip archive. By default, 'zip' format is used for all platforms except Linux", 38 | setupZip, 39 | runTaskZip, 40 | nil, 41 | map[string]interface{}{"platforms": "!linux", "include-top-level-dir": "!windows"}}) 42 | RegisterParallelizable(ParallelizableTask{ 43 | TASK_TARGZ, 44 | "Create a compressed archive. Linux-only by default", 45 | setupTarGz, 46 | runTaskTarGz, 47 | nil, 48 | map[string]interface{}{"platforms": "linux", "include-top-level-dir": "!windows"}}) 49 | 50 | } 51 | 52 | const ( 53 | TASK_ZIP = "archive-zip" 54 | TASK_TARGZ = "archive-tar-gz" 55 | ) 56 | 57 | func setupTarGz(tp TaskParams) ([]platforms.Platform, error) { 58 | //for previous versions ... 59 | //osOptions := settings.GetTaskSettingMap(TASK_ARCHIVE, "os") 60 | if _, keyExists := tp.Settings.TaskSettings[TASK_TARGZ]["os"]; keyExists { 61 | return []platforms.Platform{}, errors.New("Option 'os' is no longer supported! Please use 'platforms' instead, specified as a 'build contraint'. e.g. 'linux,386'") 62 | } 63 | bc := tp.Settings.GetTaskSettingString(TASK_TARGZ, "platforms") 64 | destPlatforms := platforms.ApplyBuildConstraints(bc, tp.DestPlatforms) 65 | return destPlatforms, nil 66 | } 67 | func setupZip(tp TaskParams) ([]platforms.Platform, error) { 68 | //for previous versions ... 69 | //osOptions := settings.GetTaskSettingMap(TASK_ARCHIVE, "os") 70 | if _, keyExists := tp.Settings.TaskSettings[TASK_ZIP]["os"]; keyExists { 71 | return []platforms.Platform{}, errors.New("Option 'os' is no longer supported! Please use 'platforms' instead, specified as a 'build contraint'. e.g. 'linux,386'") 72 | } 73 | bc := tp.Settings.GetTaskSettingString(TASK_ZIP, "platforms") 74 | destPlatforms := platforms.ApplyBuildConstraints(bc, tp.DestPlatforms) 75 | return destPlatforms, nil 76 | } 77 | 78 | func runTaskTarGz(tp TaskParams, dest platforms.Platform, errchan chan error) { 79 | bcTopLevelDir := tp.Settings.GetTaskSettingString(TASK_TARGZ, "include-top-level-dir") 80 | destPlatforms := platforms.ApplyBuildConstraints(bcTopLevelDir, []platforms.Platform{dest}) 81 | isIncludeTopLevelDir := platforms.ContainsPlatform(destPlatforms, dest) 82 | runArchiveTask(tp, dest, errchan, "tar.gz", archive.TarGz, isIncludeTopLevelDir) 83 | } 84 | 85 | func runTaskZip(tp TaskParams, dest platforms.Platform, errchan chan error) { 86 | bcTopLevelDir := tp.Settings.GetTaskSettingString(TASK_ZIP, "include-top-level-dir") 87 | destPlatforms := platforms.ApplyBuildConstraints(bcTopLevelDir, []platforms.Platform{dest}) 88 | isIncludeTopLevelDir := platforms.ContainsPlatform(destPlatforms, dest) 89 | runArchiveTask(tp, dest, errchan, "zip", archive.Zip, isIncludeTopLevelDir) 90 | } 91 | 92 | func runArchiveTask(tp TaskParams, dest platforms.Platform, errchan chan error, ending string, archiver archive.Archiver, isIncludeTopLevelDir bool) { 93 | err := archivePlat(dest.Os, dest.Arch, tp.MainDirs, tp.WorkingDirectory, tp.OutDestRoot, tp.Settings, ending, archiver, isIncludeTopLevelDir) 94 | if err != nil { 95 | //TODO - 'force' option? 96 | errchan <- err 97 | return 98 | } 99 | //always notify completion 100 | errchan <- nil 101 | } 102 | 103 | func archivePlat(goos, arch string, mainDirs []string, workingDirectory, outDestRoot string, settings *config.Settings, ending string, archiver archive.Archiver, includeTopLevelDir bool) error { 104 | resources := core.ParseIncludeResources(workingDirectory, settings.ResourcesInclude, settings.ResourcesExclude, !settings.IsQuiet()) 105 | //log.Printf("Resources: %v", resources) 106 | exes := []string{} 107 | for _, mainDir := range mainDirs { 108 | var exeName string 109 | if len(mainDirs) == 1 { 110 | exeName = settings.AppName 111 | } else { 112 | exeName = filepath.Base(mainDir) 113 | } 114 | binPath, err := core.GetAbsoluteBin(goos, arch, settings.AppName, exeName, workingDirectory, settings.GetFullVersionName(), settings.OutPath, settings.ArtifactsDest) 115 | 116 | if err != nil { 117 | return err 118 | } 119 | exes = append(exes, binPath) 120 | } 121 | outDir := filepath.Join(outDestRoot, settings.GetFullVersionName()) 122 | err := os.MkdirAll(outDir, 0777) 123 | if err != nil { 124 | return err 125 | } 126 | archivePath, err := archive.ArchiveBinariesAndResources(outDir, goos+"_"+arch, 127 | exes, settings.AppName, resources, *settings, archiver, ending, includeTopLevelDir) 128 | if err != nil { 129 | log.Printf("ZIP error: %s", err) 130 | return err 131 | } else { 132 | if !settings.IsQuiet() { 133 | log.Printf("Artifact(s) archived to %s", archivePath) 134 | } 135 | } 136 | return nil 137 | } 138 | -------------------------------------------------------------------------------- /tasks/bump.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "errors" 21 | "log" 22 | "strconv" 23 | "strings" 24 | 25 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 26 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 27 | "github.com/laher/goxc/config" 28 | "github.com/laher/goxc/core" 29 | ) 30 | 31 | const TASK_BUMP = "bump" 32 | 33 | //runs automatically 34 | func init() { 35 | Register(Task{ 36 | TASK_BUMP, 37 | "bump package version in .goxc.json. By default, the patch number (after the second dot) is increased by one. You can specify major or minor instead with -dot=0 or -dot=1", 38 | bump, 39 | map[string]interface{}{ 40 | "dot": "2"}}) 41 | } 42 | 43 | func bump(tp TaskParams) error { 44 | c, err := config.LoadJsonConfigs(tp.WorkingDirectory, []string{core.GOXC_CONFIGNAME_BASE + core.GOXC_FILE_EXT}, !tp.Settings.IsQuiet()) 45 | if err != nil { 46 | return nil 47 | } 48 | pv := c.PackageVersion 49 | if pv == core.PACKAGE_VERSION_DEFAULT || pv == "" { 50 | //go from 'default' version to 0.0.1 (or 0.1.0 or 1.0.0) 51 | pv = "0.0.0" 52 | } 53 | pvparts := strings.Split(pv, ".") 54 | partToBumpStr := tp.Settings.GetTaskSettingString(TASK_BUMP, "dot") 55 | partToBump, err := strconv.Atoi(partToBumpStr) 56 | if err != nil { 57 | return err 58 | } 59 | if partToBump < 0 { 60 | return errors.New("Could not determine which part of the version number to bump") 61 | } 62 | if len(pvparts) > partToBump { 63 | thisPart := pvparts[partToBump] 64 | thisPartNum, err := strconv.Atoi(thisPart) 65 | if err != nil { 66 | return err 67 | } 68 | thisPartNum += 1 69 | pvparts[partToBump] = strconv.Itoa(thisPartNum) 70 | for i, p := range pvparts[partToBump+1:] { 71 | _, err := strconv.Atoi(p) 72 | if err != nil { 73 | break 74 | } else { 75 | //reset smaller parts to 0 76 | pvparts[i+partToBump+1] = "0" 77 | } 78 | 79 | } 80 | pvNew := strings.Join(pvparts, ".") 81 | c.PackageVersion = pvNew 82 | if !tp.Settings.IsQuiet() { 83 | log.Printf("Bumping from %s to %s", pv, c.PackageVersion) 84 | } 85 | tp.Settings.PackageVersion = pvNew 86 | return config.WriteJsonConfig(tp.WorkingDirectory, c, "", false) 87 | } else { 88 | return errors.New("PackageVersion does not contain enough dots to bump this part of the version number") 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tasks/clean-destination.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "os" 21 | "path/filepath" 22 | ) 23 | 24 | //runs automatically 25 | func init() { 26 | Register(Task{ 27 | "clean-destination", 28 | "Delete the output directory for this version of the artifact.", 29 | runTaskCleanDestination, 30 | nil}) 31 | } 32 | 33 | func runTaskCleanDestination(tp TaskParams) error { 34 | return os.RemoveAll(filepath.Join(tp.OutDestRoot, tp.Settings.GetFullVersionName())) 35 | } 36 | -------------------------------------------------------------------------------- /tasks/codesign.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "log" 21 | "os/exec" 22 | "path/filepath" 23 | "runtime" 24 | 25 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 26 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 27 | "github.com/laher/goxc/config" 28 | "github.com/laher/goxc/core" 29 | "github.com/laher/goxc/executils" 30 | "github.com/laher/goxc/platforms" 31 | ) 32 | 33 | var codesignTask = Task{ 34 | TASK_CODESIGN, 35 | "sign code for Mac. Only Mac hosts are supported for this task.", 36 | runTaskCodesign, 37 | map[string]interface{}{"id": ""}} 38 | 39 | //runs automatically 40 | func init() { 41 | Register(codesignTask) 42 | } 43 | 44 | func runTaskCodesign(tp TaskParams) (err error) { 45 | for _, dest := range tp.DestPlatforms { 46 | for _, mainDir := range tp.MainDirs { 47 | var exeName string 48 | if len(tp.MainDirs) == 1 { 49 | exeName = tp.Settings.AppName 50 | } else { 51 | exeName = filepath.Base(mainDir) 52 | 53 | } 54 | binPath, err := core.GetAbsoluteBin(dest.Os, dest.Arch, tp.Settings.AppName, exeName, tp.WorkingDirectory, tp.Settings.GetFullVersionName(), tp.Settings.OutPath, tp.Settings.ArtifactsDest) 55 | 56 | if err != nil { 57 | return err 58 | } 59 | err = codesignPlat(dest.Os, dest.Arch, binPath, tp.Settings) 60 | } 61 | } 62 | //TODO return error 63 | return err 64 | } 65 | 66 | func codesignPlat(goos, arch string, binPath string, settings *config.Settings) error { 67 | // settings.codesign only works on OS X for binaries generated for OS X. 68 | id := settings.GetTaskSettingString("codesign", "id") 69 | if id != "" && runtime.GOOS == platforms.DARWIN && goos == platforms.DARWIN { 70 | if err := signBinary(binPath, id); err != nil { 71 | log.Printf("codesign failed: %s", err) 72 | return err 73 | } else { 74 | if !settings.IsQuiet() { 75 | log.Printf("Signed with ID: %q", id) 76 | } 77 | return nil 78 | } 79 | } 80 | return nil 81 | } 82 | 83 | func signBinary(binPath string, id string) error { 84 | cmd := exec.Command("codesign") 85 | cmd.Args = append(cmd.Args, "-s", id, binPath) 86 | 87 | return executils.StartAndWait(cmd) 88 | } 89 | -------------------------------------------------------------------------------- /tasks/copy-resources.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "io" 21 | "log" 22 | "os" 23 | "path/filepath" 24 | "strings" 25 | 26 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 27 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 28 | "github.com/laher/goxc/core" 29 | ) 30 | 31 | //runs automatically 32 | func init() { 33 | Register(Task{ 34 | TASK_COPY_RESOURCES, 35 | "Copy resources", 36 | runTaskCopyResources, 37 | nil}) 38 | } 39 | 40 | func runTaskCopyResources(tp TaskParams) error { 41 | resources := core.ParseIncludeResources(tp.WorkingDirectory, tp.Settings.ResourcesInclude, tp.Settings.ResourcesExclude, !tp.Settings.IsQuiet()) 42 | destFolder := filepath.Join(tp.OutDestRoot, tp.Settings.GetFullVersionName()) 43 | if !tp.Settings.IsQuiet() { 44 | log.Printf("resources: %v", resources) 45 | } 46 | for _, resource := range resources { 47 | if strings.HasPrefix(resource, tp.WorkingDirectory) { 48 | resource = resource[len(tp.WorkingDirectory)+1:] 49 | } 50 | //_, resourcebase := filepath.Split(resource) 51 | sourcePath := filepath.Join(tp.WorkingDirectory, resource) 52 | destPath := filepath.Join(destFolder, resource) 53 | finfo, err := os.Lstat(sourcePath) 54 | if err != nil { 55 | return err 56 | } 57 | if finfo.IsDir() { 58 | err = os.MkdirAll(destPath, 0777) 59 | if err != nil && !os.IsExist(err) { 60 | return err 61 | } 62 | } else { 63 | err = os.MkdirAll(filepath.Dir(destPath), 0777) 64 | if err != nil && !os.IsExist(err) { 65 | return err 66 | } 67 | _, err = copyFile(sourcePath, destPath, !tp.Settings.IsQuiet()) 68 | } 69 | if err != nil { 70 | return err 71 | } 72 | } 73 | return nil 74 | } 75 | 76 | func copyDir(srcDir, destDir string, isVerbose bool) (fileCount int, err error) { 77 | fileCount = 0 78 | err = os.MkdirAll(destDir, 0777) 79 | if err != nil && !os.IsExist(err) { 80 | return fileCount, err 81 | } 82 | err = filepath.Walk(srcDir, func(path string, fi os.FileInfo, err error) error { 83 | fileCount++ 84 | base := strings.Replace(path, srcDir, "", 1) 85 | dest := filepath.Join(destDir, base) 86 | if fi.IsDir() { 87 | err := os.Mkdir(dest, 0777) 88 | if os.IsExist(err) { 89 | return nil 90 | } else { 91 | return err 92 | } 93 | } else { 94 | if isVerbose { 95 | log.Printf("path: %s, base: %s", path, base) 96 | } 97 | _, err := copyFile(path, dest, isVerbose) 98 | return err 99 | } 100 | }) 101 | return 102 | } 103 | 104 | func copyFile(srcName, dstName string, isVerbose bool) (written int64, err error) { 105 | if isVerbose { 106 | log.Printf("Copying file %s to %s", srcName, dstName) 107 | } 108 | src, err := os.Open(srcName) 109 | if err != nil { 110 | return 111 | } 112 | defer src.Close() 113 | 114 | dst, err := os.Create(dstName) 115 | if err != nil { 116 | return 117 | } 118 | defer dst.Close() 119 | 120 | return io.Copy(dst, src) 121 | } 122 | -------------------------------------------------------------------------------- /tasks/deb-dev.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "fmt" 21 | 22 | "log" 23 | "os" 24 | "path/filepath" 25 | "strings" 26 | 27 | "github.com/debber/debber-v0.3/deb" 28 | "github.com/debber/debber-v0.3/debgen" 29 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 30 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 31 | "github.com/laher/goxc/platforms" 32 | "github.com/laher/goxc/typeutils" 33 | ) 34 | 35 | //runs automatically 36 | func init() { 37 | Register(Task{ 38 | TASK_DEB_DEV, 39 | "Build a '-dev.deb' sources package for use as a Debian/Ubuntu Linux dependency.", 40 | runTaskDebDev, 41 | map[string]interface{}{ 42 | "metadata": map[string]interface{}{ 43 | "maintainer": "unknown", 44 | "maintainerEmail": "unknown@example.com", 45 | }, 46 | "metadata-deb": map[string]interface{}{"Depends": "", 47 | "Build-Depends": "debhelper (>=4.0.0)", 48 | }, 49 | "rmtemp": true, 50 | "armarch": "", 51 | "go-sources-dir": ".", 52 | "other-mappped-files": map[string]interface{}{}, 53 | }, 54 | }) 55 | } 56 | 57 | func runTaskDebDev(tp TaskParams) (err error) { 58 | build := false 59 | for _, dest := range tp.DestPlatforms { 60 | if dest.Os == platforms.LINUX { 61 | build = true 62 | } 63 | } 64 | if build { 65 | err := debDevBuild(tp) 66 | if err != nil { 67 | log.Printf("Error: %v", err) 68 | } 69 | } 70 | return 71 | } 72 | 73 | func debDevBuild(tp TaskParams) error { 74 | metadata := tp.Settings.GetTaskSettingMap(TASK_DEB_GEN, "metadata") 75 | //maintain support for old configs ... 76 | metadataDebX := tp.Settings.GetTaskSettingMap(TASK_DEB_GEN, "metadata-deb") 77 | otherMappedFilesFromSetting := tp.Settings.GetTaskSettingMap(TASK_DEB_GEN, "other-mapped-files") 78 | otherMappedFiles, err := calcOtherMappedFiles(otherMappedFilesFromSetting) 79 | if err != nil { 80 | return err 81 | } 82 | metadataDeb := map[string]string{} 83 | for k, v := range metadataDebX { 84 | val, ok := v.(string) 85 | if ok { 86 | metadataDeb[k] = val 87 | } 88 | } 89 | rmtemp := tp.Settings.GetTaskSettingBool(TASK_DEB_GEN, "rmtemp") 90 | debDir := filepath.Join(tp.OutDestRoot, tp.Settings.GetFullVersionName()) //v0.8.1 dont use platform dir 91 | tmpDir := filepath.Join(debDir, ".goxc-temp") 92 | 93 | shortDescription := "?" 94 | if desc, keyExists := metadata["description"]; keyExists { 95 | var err error 96 | shortDescription, err = typeutils.ToString(desc, "description") 97 | if err != nil { 98 | return err 99 | } 100 | } 101 | longDescription := " " 102 | if ldesc, keyExists := metadata["long-description"]; keyExists { 103 | var err error 104 | longDescription, err = typeutils.ToString(ldesc, "long-description") 105 | if err != nil { 106 | return err 107 | } 108 | } 109 | maintainerName := "?" 110 | if maint, keyExists := metadata["maintainer"]; keyExists { 111 | var err error 112 | maintainerName, err = typeutils.ToString(maint, "maintainer") 113 | if err != nil { 114 | return err 115 | } 116 | } 117 | maintainerEmail := "example@example.org" 118 | if maintEmail, keyExists := metadata["maintainer-email"]; keyExists { 119 | var err error 120 | maintainerEmail, err = typeutils.ToString(maintEmail, "maintainer-email") 121 | if err != nil { 122 | return err 123 | } 124 | } 125 | //'dev' Package should be a separate task 126 | addDevPackage := false 127 | 128 | build := debgen.NewBuildParams() 129 | build.DestDir = debDir 130 | build.TmpDir = tmpDir 131 | build.Init() 132 | build.IsRmtemp = rmtemp 133 | var ctrl *deb.Control 134 | //Read control data. If control file doesnt exist, use parameters ... 135 | fi, err := os.Open(filepath.Join(build.DebianDir, "control")) 136 | if os.IsNotExist(err) { 137 | log.Printf("WARNING - no debian 'control' file found. Use `debber` to generate proper debian metadata") 138 | ctrl = deb.NewControlDefault(tp.AppName, maintainerName, maintainerEmail, shortDescription, longDescription, addDevPackage) 139 | for _, c := range *ctrl { 140 | for k, v := range metadataDeb { 141 | c.Set(k, v) 142 | } 143 | } 144 | } else if err != nil { 145 | return fmt.Errorf("%v", err) 146 | } else { 147 | cfr := deb.NewControlFileReader(fi) 148 | ctrl, err = cfr.Parse() 149 | if err != nil { 150 | return fmt.Errorf("%v", err) 151 | } 152 | } 153 | goSourcesDir := tp.Settings.GetTaskSettingString(TASK_DEB_GEN, "go-sources-dir") 154 | mappedSources, err := debgen.GlobForGoSources(goSourcesDir, []string{build.DestDir, build.TmpDir}) 155 | if err != nil { 156 | return fmt.Errorf("%v", err) 157 | } 158 | for k, v := range mappedSources { 159 | otherMappedFiles[k] = v 160 | } 161 | debArch := deb.ArchAll 162 | build.Arches = []deb.Architecture{debArch} 163 | build.Version = tp.Settings.GetFullVersionName() 164 | dgens, err := debgen.PrepareBasicDebGen(ctrl, build) 165 | if err != nil { 166 | return fmt.Errorf("Error preparing deb generator: %v", err) 167 | } 168 | //there should only be one for this platform. 169 | // Anyway this part maps all binaries. 170 | for _, dgen := range dgens { 171 | if strings.HasSuffix(dgen.DebWriter.Control.Get(deb.PackageFName), "-dev") { 172 | for k, v := range otherMappedFiles { 173 | dgen.DataFiles[k] = v 174 | } 175 | err = dgen.GenerateAllDefault() 176 | if err != nil { 177 | return fmt.Errorf("Error generating deb: %v", err) 178 | } 179 | if !tp.Settings.IsQuiet() { 180 | log.Printf("Wrote -dev deb to %s", filepath.Join(build.DestDir, dgen.DebWriter.Filename)) 181 | } 182 | } 183 | } 184 | return err 185 | 186 | } 187 | -------------------------------------------------------------------------------- /tasks/deb-source.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | /* Nearing completion */ 20 | //TODO: various refinements ... 21 | import ( 22 | /* 23 | "bytes" 24 | "crypto/md5" 25 | "crypto/sha1" 26 | "crypto/sha256" 27 | "encoding/hex" 28 | //Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 29 | //see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 30 | "github.com/laher/goxc/archive" 31 | //"github.com/laher/goxc/packaging/sdeb" 32 | */ 33 | "github.com/debber/debber-v0.3/deb" 34 | "github.com/debber/debber-v0.3/debgen" 35 | "github.com/laher/goxc/platforms" 36 | "github.com/laher/goxc/typeutils" 37 | // "io" 38 | // "io/ioutil" 39 | "log" 40 | "os" 41 | "path/filepath" 42 | //"strings" 43 | // "text/template" 44 | // "time" 45 | "fmt" 46 | ) 47 | 48 | /* 49 | type Checksum struct { 50 | Checksum string 51 | Size int64 52 | File string 53 | } 54 | type TemplateVars struct { 55 | PackageName string 56 | PackageVersion string 57 | BuildDepends string 58 | Priority string 59 | Maintainer string 60 | MaintainerEmail string 61 | StandardsVersion string 62 | Architecture string 63 | Section string 64 | Depends string 65 | Description string 66 | Other string 67 | Status string 68 | EntryDate string 69 | Format string 70 | Files []Checksum 71 | ChecksumsSha1 []Checksum 72 | ChecksumsSha256 []Checksum 73 | } 74 | */ 75 | 76 | //runs automatically 77 | func init() { 78 | Register(Task{ 79 | TASK_DEB_SOURCE, 80 | "Build a source package. Currently only supports 'source deb' format for Debian/Ubuntu Linux.", 81 | runTaskPkgSource, 82 | map[string]interface{}{ 83 | "metadata": map[string]interface{}{ 84 | "maintainer": "unknown", 85 | "maintainerEmail": "unknown@example.com", 86 | }, 87 | "metadata-deb": map[string]interface{}{ 88 | "Depends": "", 89 | "Build-Depends": "debhelper (>=4.0.0), golang-go, gcc", 90 | }, 91 | "rmtemp": true, 92 | "go-sources-dir": ".", 93 | "other-mappped-files": map[string]interface{}{}, 94 | }}) 95 | } 96 | 97 | func runTaskPkgSource(tp TaskParams) (err error) { 98 | var makeSourceDeb bool 99 | for _, dest := range tp.DestPlatforms { 100 | if dest.Os == platforms.LINUX { 101 | makeSourceDeb = true 102 | } 103 | } 104 | //TODO rpm 105 | if makeSourceDeb { 106 | if tp.Settings.IsVerbose() { 107 | log.Printf("Building 'source deb' for Ubuntu/Debian Linux.") 108 | } 109 | // log.Printf("WARNING: 'source deb' functionality requires more documentation and config options to make it properly useful. More coming soon.") 110 | err = debSourceBuild(tp) 111 | if err != nil { 112 | return 113 | } 114 | } else { 115 | if !tp.Settings.IsQuiet() { 116 | log.Printf("Not building source debs because Linux has not been selected as a target OS") 117 | } 118 | } 119 | //OK 120 | return 121 | } 122 | 123 | /* 124 | func checksums(path, name string) (*Checksum, *Checksum, *Checksum, error) { 125 | //checksums 126 | f, err := os.Open(path) 127 | if err != nil { 128 | return nil, nil, nil, err 129 | } 130 | 131 | hashMd5 := md5.New() 132 | size, err := io.Copy(hashMd5, f) 133 | if err != nil { 134 | return nil, nil, nil, err 135 | } 136 | checksumMd5 := Checksum{hex.EncodeToString(hashMd5.Sum(nil)), size, name} 137 | 138 | f.Seek(int64(0), 0) 139 | hash256 := sha256.New() 140 | size, err = io.Copy(hash256, f) 141 | if err != nil { 142 | return nil, nil, nil, err 143 | } 144 | checksumSha256 := Checksum{hex.EncodeToString(hash256.Sum(nil)), size, name} 145 | 146 | f.Seek(int64(0), 0) 147 | hash1 := sha1.New() 148 | size, err = io.Copy(hash1, f) 149 | if err != nil { 150 | return nil, nil, nil, err 151 | } 152 | checksumSha1 := Checksum{hex.EncodeToString(hash1.Sum(nil)), size, name} 153 | 154 | err = f.Close() 155 | if err != nil { 156 | return nil, nil, nil, err 157 | } 158 | 159 | return &checksumMd5, &checksumSha1, &checksumSha256, nil 160 | 161 | } 162 | */ 163 | func debSourceBuild(tp TaskParams) (err error) { 164 | metadata := tp.Settings.GetTaskSettingMap(TASK_DEB_SOURCE, "metadata") 165 | //armArchName := getArmArchName(tp.Settings) 166 | metadataDebX := tp.Settings.GetTaskSettingMap(TASK_DEB_SOURCE, "metadata-deb") 167 | otherMappedFilesFromSetting := tp.Settings.GetTaskSettingMap(TASK_DEB_GEN, "other-mapped-files") 168 | otherMappedFiles, err := calcOtherMappedFiles(otherMappedFilesFromSetting) 169 | if err != nil { 170 | return err 171 | } 172 | metadataDeb := map[string]string{} 173 | for k, v := range metadataDebX { 174 | val, ok := v.(string) 175 | if ok { 176 | metadataDeb[k] = val 177 | } 178 | } 179 | rmtemp := tp.Settings.GetTaskSettingBool(TASK_DEB_SOURCE, "rmtemp") 180 | debDir := filepath.Join(tp.OutDestRoot, tp.Settings.GetFullVersionName()) //v0.8.1 dont use platform dir 181 | tmpDir := filepath.Join(debDir, ".goxc-temp") 182 | 183 | shortDescription := "?" 184 | if desc, keyExists := metadata["description"]; keyExists { 185 | var err error 186 | shortDescription, err = typeutils.ToString(desc, "description") 187 | if err != nil { 188 | return err 189 | } 190 | } 191 | longDescription := " " 192 | if ldesc, keyExists := metadata["long-description"]; keyExists { 193 | var err error 194 | longDescription, err = typeutils.ToString(ldesc, "long-description") 195 | if err != nil { 196 | return err 197 | } 198 | } 199 | maintainerName := "?" 200 | if maint, keyExists := metadata["maintainer"]; keyExists { 201 | var err error 202 | maintainerName, err = typeutils.ToString(maint, "maintainer") 203 | if err != nil { 204 | return err 205 | } 206 | } 207 | maintainerEmail := "example@example.org" 208 | if maintEmail, keyExists := metadata["maintainer-email"]; keyExists { 209 | var err error 210 | maintainerEmail, err = typeutils.ToString(maintEmail, "maintainer-email") 211 | if err != nil { 212 | return err 213 | } 214 | } 215 | 216 | build := debgen.NewBuildParams() 217 | build.DestDir = debDir 218 | build.TmpDir = tmpDir 219 | build.Init() 220 | build.IsRmtemp = rmtemp 221 | var ctrl *deb.Control 222 | //Read control data. If control file doesnt exist, use parameters ... 223 | fi, err := os.Open(filepath.Join(build.DebianDir, "control")) 224 | if os.IsNotExist(err) { 225 | log.Printf("WARNING - no debian 'control' file found. Use `debber` to generate proper debian metadata") 226 | ctrl = deb.NewControlDefault(tp.AppName, maintainerName, maintainerEmail, shortDescription, longDescription, false) 227 | } else if err != nil { 228 | return fmt.Errorf("%v", err) 229 | } else { 230 | cfr := deb.NewControlFileReader(fi) 231 | ctrl, err = cfr.Parse() 232 | if err != nil { 233 | return fmt.Errorf("%v", err) 234 | } 235 | } 236 | goSourcesDir := tp.Settings.GetTaskSettingString(TASK_DEB_SOURCE, "go-sources-dir") 237 | mappedSources, err := debgen.GlobForGoSources(goSourcesDir, []string{build.DestDir, build.TmpDir}) 238 | if err != nil { 239 | return fmt.Errorf("%v", err) 240 | } 241 | for k, v := range mappedSources { 242 | otherMappedFiles[k] = v 243 | } 244 | build.Version = tp.Settings.GetFullVersionName() 245 | spgen, err := debgen.PrepareSourceDebGenerator(ctrl, build) 246 | if spgen.OrigFiles == nil { 247 | spgen.OrigFiles = map[string]string{} 248 | } 249 | for k, v := range otherMappedFiles { 250 | spgen.OrigFiles[k] = v 251 | } 252 | if err != nil { 253 | return fmt.Errorf("%v", err) 254 | } 255 | err = spgen.GenerateAllDefault() 256 | if err != nil { 257 | return fmt.Errorf("%v", err) 258 | } 259 | if tp.Settings.IsVerbose() { 260 | log.Printf("Wrote dsc file to %s", filepath.Join(build.DestDir, spgen.SourcePackage.DscFileName)) 261 | log.Printf("Wrote orig file to %s", filepath.Join(build.DestDir, spgen.SourcePackage.OrigFileName)) 262 | log.Printf("Wrote debian file to %s", filepath.Join(build.DestDir, spgen.SourcePackage.DebianFileName)) 263 | } 264 | return nil 265 | } 266 | -------------------------------------------------------------------------------- /tasks/deb.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "fmt" 21 | "log" 22 | "os" 23 | "path/filepath" 24 | "strings" 25 | 26 | "github.com/debber/debber-v0.3/deb" 27 | "github.com/debber/debber-v0.3/debgen" 28 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 29 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 30 | "github.com/laher/goxc/config" 31 | "github.com/laher/goxc/core" 32 | "github.com/laher/goxc/platforms" 33 | "github.com/laher/goxc/typeutils" 34 | ) 35 | 36 | //runs automatically 37 | func init() { 38 | Register(Task{ 39 | TASK_DEB_GEN, 40 | "Build a .deb package for Debian/Ubuntu Linux.", 41 | runTaskDebGen, 42 | map[string]interface{}{ 43 | "metadata": map[string]interface{}{ 44 | "maintainer": "unknown", 45 | "maintainerEmail": "unknown@example.com", 46 | }, 47 | "metadata-deb": map[string]interface{}{"Depends": "", 48 | "Build-Depends": "debhelper (>=4.0.0), golang-go, gcc", 49 | }, 50 | "rmtemp": true, 51 | "armarch": "", 52 | "go-sources-dir": ".", 53 | "other-mappped-files": map[string]interface{}{}, 54 | "bin-dir": "/usr/bin", 55 | }, 56 | }) 57 | } 58 | 59 | func runTaskDebGen(tp TaskParams) (err error) { 60 | for _, dest := range tp.DestPlatforms { 61 | err := pkgDebPlat(dest, tp) 62 | if err != nil { 63 | log.Printf("Error: %v", err) 64 | } 65 | } 66 | return 67 | } 68 | 69 | func pkgDebPlat(dest platforms.Platform, tp TaskParams) (err error) { 70 | if dest.Os == platforms.LINUX { 71 | return debBuild(dest, tp) 72 | } 73 | return nil 74 | } 75 | 76 | // TODO fix armhf/armel distinction ... 77 | func getDebArch(destArch string, armArchName string) deb.Architecture { 78 | var architecture deb.Architecture 79 | switch destArch { 80 | case platforms.X86: 81 | architecture = deb.ArchI386 82 | case platforms.ARM: 83 | architecture = deb.ArchArmhf 84 | case platforms.AMD64: 85 | architecture = deb.ArchAmd64 86 | } 87 | return architecture 88 | } 89 | 90 | func getArmArchName(settings *config.Settings) string { 91 | armArchName := settings.GetTaskSettingString(TASK_DEB_GEN, "armarch") 92 | if armArchName == "" { 93 | //derive it from GOARM version: 94 | goArm := settings.GetTaskSettingString(TASK_XC, "GOARM") 95 | if goArm == "5" { 96 | armArchName = "armel" 97 | } else { 98 | armArchName = "armhf" 99 | } 100 | } 101 | return armArchName 102 | } 103 | 104 | func calcOtherMappedFiles(otherMappedFilesFromSetting map[string]interface{}) (map[string]string, error) { 105 | 106 | otherMappedFiles := map[string]string{} 107 | if otherMappedFilesFromSetting != nil { 108 | for k, v := range otherMappedFilesFromSetting { 109 | val, ok := v.(string) 110 | if ok { 111 | finf, err := os.Stat(val) 112 | if err != nil { 113 | return otherMappedFiles, err 114 | } 115 | if finf.IsDir() { 116 | filepath.Walk(val, func(path string, info os.FileInfo, err error) error { 117 | if !info.IsDir() { 118 | kpath, err := filepath.Rel(val, path) 119 | if err != nil { 120 | return err 121 | } 122 | var key string 123 | if strings.HasSuffix(k, "/") { 124 | key = k + kpath 125 | } else { 126 | key = k + "/" + kpath 127 | } 128 | otherMappedFiles[key] = path 129 | } 130 | return nil 131 | }) 132 | } else { 133 | otherMappedFiles[k] = val 134 | } 135 | } 136 | } 137 | } 138 | return otherMappedFiles, nil 139 | } 140 | 141 | func debBuild(dest platforms.Platform, tp TaskParams) error { 142 | metadata := tp.Settings.GetTaskSettingMap(TASK_DEB_GEN, "metadata") 143 | armArchName := getArmArchName(tp.Settings) 144 | //maintain support for old configs ... 145 | metadataDebX := tp.Settings.GetTaskSettingMap(TASK_DEB_GEN, "metadata-deb") 146 | otherMappedFilesFromSetting := tp.Settings.GetTaskSettingMap(TASK_DEB_GEN, "other-mapped-files") 147 | otherMappedFiles, err := calcOtherMappedFiles(otherMappedFilesFromSetting) 148 | if err != nil { 149 | return err 150 | } 151 | 152 | if tp.Settings.IsVerbose() { 153 | log.Printf("other mapped files: %+v", otherMappedFiles) 154 | } 155 | metadataDeb := map[string]string{} 156 | for k, v := range metadataDebX { 157 | val, ok := v.(string) 158 | if ok { 159 | metadataDeb[k] = val 160 | } 161 | } 162 | rmtemp := tp.Settings.GetTaskSettingBool(TASK_DEB_GEN, "rmtemp") 163 | debDir := filepath.Join(tp.OutDestRoot, tp.Settings.GetFullVersionName()) //v0.8.1 dont use platform dir 164 | tmpDir := filepath.Join(debDir, ".goxc-temp") 165 | 166 | shortDescription := "?" 167 | if desc, keyExists := metadata["description"]; keyExists { 168 | var err error 169 | shortDescription, err = typeutils.ToString(desc, "description") 170 | if err != nil { 171 | return err 172 | } 173 | } 174 | longDescription := "" 175 | if ldesc, keyExists := metadata["long-description"]; keyExists { 176 | var err error 177 | longDescription, err = typeutils.ToString(ldesc, "long-description") 178 | if err != nil { 179 | return err 180 | } 181 | } 182 | maintainerName := "?" 183 | if maint, keyExists := metadata["maintainer"]; keyExists { 184 | var err error 185 | maintainerName, err = typeutils.ToString(maint, "maintainer") 186 | if err != nil { 187 | return err 188 | } 189 | } 190 | maintainerEmail := "example@example.org" 191 | if maintEmail, keyExists := metadata["maintainer-email"]; keyExists { 192 | var err error 193 | maintainerEmail, err = typeutils.ToString(maintEmail, "maintainer-email") 194 | if err != nil { 195 | return err 196 | } 197 | } 198 | //'dev' Package should be a separate task 199 | addDevPackage := false 200 | 201 | build := debgen.NewBuildParams() 202 | build.DestDir = debDir 203 | build.TmpDir = tmpDir 204 | build.Init() 205 | build.IsRmtemp = rmtemp 206 | var ctrl *deb.Control 207 | //Read control data. If control file doesnt exist, use parameters ... 208 | fi, err := os.Open(filepath.Join(build.DebianDir, "control")) 209 | if os.IsNotExist(err) { 210 | log.Printf("WARNING - no debian 'control' file found. Use `debber` to generate proper debian metadata") 211 | ctrl = deb.NewControlDefault(tp.AppName, maintainerName, maintainerEmail, shortDescription, longDescription, addDevPackage) 212 | for _, c := range *ctrl { 213 | for k, v := range metadataDeb { 214 | c.Set(k, v) 215 | } 216 | } 217 | } else if err != nil { 218 | return fmt.Errorf("%v", err) 219 | } else { 220 | cfr := deb.NewControlFileReader(fi) 221 | ctrl, err = cfr.Parse() 222 | if err != nil { 223 | return fmt.Errorf("%v", err) 224 | } 225 | } 226 | debArch := getDebArch(dest.Arch, armArchName) 227 | build.Arches = []deb.Architecture{debArch} 228 | build.Version = tp.Settings.GetFullVersionName() 229 | dgens, err := debgen.PrepareBasicDebGen(ctrl, build) 230 | if err != nil { 231 | return fmt.Errorf("Error preparing deb generator: %v", err) 232 | } 233 | binDir := tp.Settings.GetTaskSettingString(TASK_DEB_GEN, "bin-dir") 234 | //there should only be one for this platform. 235 | // Anyway this part maps all binaries. 236 | for _, dgen := range dgens { 237 | // -dev paragraphs handled by 'deb-dev' task. 238 | if !strings.HasSuffix(dgen.DebWriter.Control.Get(deb.PackageFName), "-dev") { 239 | for _, mainDir := range tp.MainDirs { 240 | var exeName string 241 | if len(tp.MainDirs) == 1 { 242 | exeName = tp.Settings.AppName 243 | } else { 244 | exeName = filepath.Base(mainDir) 245 | } 246 | binPath, err := core.GetAbsoluteBin(dest.Os, dest.Arch, tp.Settings.AppName, exeName, tp.WorkingDirectory, tp.Settings.GetFullVersionName(), tp.Settings.OutPath, tp.Settings.ArtifactsDest) 247 | if err != nil { 248 | return err 249 | } 250 | if dgen.DataFiles == nil { 251 | dgen.DataFiles = map[string]string{} 252 | } 253 | dgen.DataFiles["."+binDir+"/"+exeName] = binPath 254 | } 255 | for k, v := range otherMappedFiles { 256 | dgen.DataFiles[k] = v 257 | } 258 | err = dgen.GenerateAllDefault() 259 | if err != nil { 260 | return fmt.Errorf("Error generating deb: %v", err) 261 | } 262 | if !tp.Settings.IsQuiet() { 263 | log.Printf("Wrote deb to %s", filepath.Join(build.DestDir, dgen.DebWriter.Filename)) 264 | } 265 | } 266 | } 267 | return err 268 | 269 | } 270 | -------------------------------------------------------------------------------- /tasks/downloads-page.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | htemplate "html/template" 21 | "os" 22 | "path/filepath" 23 | "strings" 24 | "text/template" 25 | ) 26 | 27 | //runs automatically 28 | func init() { 29 | Register(Task{ 30 | TASK_DOWNLOADS_PAGE, 31 | "Generate a downloads page from a template. Default outputs a Markdown page.", 32 | runTaskDownloadsPage, 33 | map[string]interface{}{ 34 | "filename": "downloads.md", 35 | "outputFormat": "by-file-extension", // use by-file-extension, markdown or html 36 | "templateText": `--- 37 | layout: default 38 | title: Downloads 39 | --- 40 | 41 | {{.AppName}} downloads (version {{.Version}}) 42 | 43 | {{range $k, $v := .Categories}}### {{$k}} 44 | 45 | {{range $v}} * [{{.Text}}]({{.RelativeLink}}) 46 | {{end}} 47 | {{end}} 48 | 49 | {{.ExtraVars.footer}}`, 50 | "templateFile": "", 51 | "templateExtraVars": map[string]interface{}{"footer": "Generated by goxc"}}}) 52 | 53 | } 54 | 55 | type Download struct { 56 | Text string 57 | Version string 58 | RelativeLink string 59 | } 60 | type Report struct { 61 | AppName string 62 | Version string 63 | Categories map[string]*[]Download 64 | ExtraVars map[string]interface{} 65 | } 66 | 67 | func runTaskDownloadsPage(tp TaskParams) error { 68 | outFilename := tp.Settings.GetTaskSettingString(TASK_DOWNLOADS_PAGE, "filename") 69 | templateText := tp.Settings.GetTaskSettingString(TASK_DOWNLOADS_PAGE, "templateText") 70 | templateFile := tp.Settings.GetTaskSettingString(TASK_DOWNLOADS_PAGE, "templateFile") 71 | format := tp.Settings.GetTaskSettingString(TASK_DOWNLOADS_PAGE, "outputFormat") 72 | if format == "by-file-extension" { 73 | if strings.HasSuffix(outFilename, ".md") || strings.HasSuffix(outFilename, ".markdown") { 74 | format = "markdown" 75 | } else if strings.HasSuffix(outFilename, ".html") || strings.HasSuffix(outFilename, ".htm") { 76 | format = "html" 77 | } else { 78 | //unknown ... 79 | format = "" 80 | } 81 | } 82 | templateVars := tp.Settings.GetTaskSettingMap(TASK_DOWNLOADS_PAGE, "templateExtraVars") 83 | reportFilename := filepath.Join(tp.OutDestRoot, tp.Settings.GetFullVersionName(), outFilename) 84 | flags := os.O_WRONLY | os.O_TRUNC | os.O_CREATE 85 | out, err := os.OpenFile(reportFilename, flags, 0600) 86 | if err != nil { 87 | return err 88 | } 89 | defer out.Close() 90 | report := Report{tp.AppName, tp.Settings.GetFullVersionName(), map[string]*[]Download{}, templateVars} 91 | versionDir := filepath.Join(tp.OutDestRoot, tp.Settings.GetFullVersionName()) 92 | err = filepath.Walk(versionDir, func(path string, info os.FileInfo, e error) error { 93 | return downloadsWalkFunc(path, tp.Settings.GetFullVersionName(), info, e, tp, report, outFilename, format) 94 | }) 95 | if err != nil { 96 | return err 97 | } 98 | err = RunTemplate(reportFilename, templateFile, templateText, out, report, format) 99 | if err != nil { 100 | return err 101 | } 102 | return out.Close() 103 | } 104 | func RunTemplate(reportFilename, templateFile, templateText string, out *os.File, data interface{}, format string) (err error) { 105 | var tmpl *template.Template 106 | var htmpl *htemplate.Template 107 | if templateFile != "" { 108 | if format == "html" { 109 | htmpl, err = htemplate.New(templateFile).ParseGlob(templateFile) 110 | } else { 111 | tmpl, err = template.New(templateFile).ParseGlob(templateFile) 112 | } 113 | } else { 114 | if format == "html" { 115 | htmpl, err = htemplate.New(reportFilename).Parse(templateText) 116 | } else { 117 | tmpl, err = template.New(reportFilename).Parse(templateText) 118 | } 119 | } 120 | if err != nil { 121 | return err 122 | } 123 | if format == "html" { 124 | err = htmpl.Execute(out, data) 125 | } else { 126 | err = tmpl.Execute(out, data) 127 | } 128 | if err != nil { 129 | return err 130 | } 131 | 132 | return err 133 | } 134 | 135 | func GetCategory(relativePath string) string { 136 | category := "Other files" 137 | if strings.Contains(relativePath, "linux") || strings.HasSuffix(relativePath, ".deb") { 138 | category = "Linux" 139 | } else if strings.Contains(relativePath, "darwin") { 140 | category = "Darwin (Apple Mac)" 141 | } else if strings.Contains(relativePath, "netbsd") { 142 | category = "NetBSD" 143 | } else if strings.Contains(relativePath, "freebsd") { 144 | category = "FreeBSD" 145 | } else if strings.Contains(relativePath, "windows") { 146 | category = "MS Windows" 147 | } else if strings.Contains(relativePath, "openbsd") { 148 | category = "OpenBSD" 149 | } else if strings.Contains(relativePath, "plan9") { 150 | category = "Plan 9" 151 | } 152 | return category 153 | } 154 | 155 | func downloadsWalkFunc(fullPath string, Version string, fi2 os.FileInfo, err error, tp TaskParams, report Report, reportFilename, format string) error { 156 | if fi2.IsDir() || fi2.Name() == reportFilename { 157 | return nil 158 | } 159 | 160 | versionDir := filepath.Join(tp.OutDestRoot, tp.Settings.GetFullVersionName()) 161 | relativePath := strings.Replace(fullPath, versionDir, "", -1) 162 | relativePath = strings.Replace(relativePath, "\\", "/", -1) 163 | relativePath = strings.TrimPrefix(relativePath, "/") 164 | text := fi2.Name() 165 | if format == "markdown" { 166 | text = strings.Replace(text, "_", "\\_", -1) 167 | } 168 | category := GetCategory(relativePath) 169 | 170 | //log.Printf("Adding: %s", relativePath) 171 | download := Download{text, Version, relativePath} 172 | v, ok := report.Categories[category] 173 | var existing []Download 174 | if !ok { 175 | existing = []Download{} 176 | } else { 177 | existing = *v 178 | } 179 | 180 | existing = append(existing, download) 181 | report.Categories[category] = &existing 182 | //entryCount++ 183 | return nil 184 | } 185 | -------------------------------------------------------------------------------- /tasks/github/github.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | //TODO: handle conflicts (delete or skip?) 19 | //TODO: own options for downloadspage 20 | import ( 21 | "bytes" 22 | "encoding/json" 23 | "errors" 24 | "fmt" 25 | "log" 26 | "os" 27 | "path/filepath" 28 | "strings" 29 | 30 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 31 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 32 | "github.com/laher/goxc/core" 33 | "github.com/laher/goxc/tasks" 34 | "github.com/laher/goxc/tasks/httpc" 35 | ) 36 | 37 | func RunTaskPubGH(tp tasks.TaskParams) error { 38 | owner := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "owner") 39 | apikey := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "apikey") 40 | repository := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "repository") 41 | apiHost := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "apihost") 42 | //downloadsHost := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "downloadshost") 43 | versionDir := filepath.Join(tp.OutDestRoot, tp.Settings.GetFullVersionName()) 44 | 45 | missing := []string{} 46 | 47 | if owner == "" { 48 | missing = append(missing, "owner") 49 | } 50 | if apikey == "" { 51 | missing = append(missing, "apikey") 52 | } 53 | if repository == "" { 54 | missing = append(missing, "repository") 55 | } 56 | if apiHost == "" { 57 | missing = append(missing, "apihost") 58 | } 59 | if len(missing) > 0 { 60 | return errors.New(fmt.Sprintf("github configuration missing (%v)", missing)) 61 | } 62 | outFilename := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "downloadspage") 63 | templateText := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "templateText") 64 | templateFile := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "templateFile") 65 | format := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "outputFormat") 66 | body := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "body") 67 | preRelease := tp.Settings.GetTaskSettingBool(tasks.TASK_PUBLISH_GITHUB, "prerelease") 68 | if format == "by-file-extension" { 69 | if strings.HasSuffix(outFilename, ".md") || strings.HasSuffix(outFilename, ".markdown") { 70 | format = "markdown" 71 | } else if strings.HasSuffix(outFilename, ".html") || strings.HasSuffix(outFilename, ".htm") { 72 | format = "html" 73 | } else { 74 | //unknown ... 75 | format = "" 76 | } 77 | } 78 | templateVars := tp.Settings.GetTaskSettingMap(tasks.TASK_DOWNLOADS_PAGE, "templateExtraVars") 79 | reportFilename := filepath.Join(tp.OutDestRoot, tp.Settings.GetFullVersionName(), outFilename) 80 | _, err := os.Stat(filepath.Dir(reportFilename)) 81 | if err != nil { 82 | if os.IsNotExist(err) { 83 | return errors.New("No artifacts built for this version yet. Please build some artifacts before running the 'publish-github' task") 84 | } else { 85 | return err 86 | } 87 | } 88 | prefix := tp.Settings.GetTaskSettingString(tasks.TASK_TAG, "prefix") 89 | tagName := prefix + tp.Settings.GetFullVersionName() 90 | err = createRelease(apiHost, owner, apikey, repository, tagName, tp.Settings.GetFullVersionName(), body, preRelease, tp.Settings.IsVerbose()) 91 | if err != nil { 92 | if serr, ok := err.(httpc.HttpError); ok { 93 | if serr.StatusCode == 422 { 94 | //existing release. ignore. 95 | if !tp.Settings.IsQuiet() { 96 | log.Printf("Note: release already exists. %v", serr) 97 | } 98 | } else { 99 | return err 100 | } 101 | } else { 102 | return err 103 | } 104 | } 105 | report := tasks.BtReport{ 106 | AppName: tp.AppName, 107 | Version: tp.Settings.GetFullVersionName(), 108 | Categories: map[string]*[]tasks.BtDownload{}, 109 | ExtraVars: templateVars} 110 | flags := os.O_WRONLY | os.O_TRUNC | os.O_CREATE 111 | out, err := os.OpenFile(reportFilename, flags, 0600) 112 | if err != nil { 113 | return err 114 | } 115 | defer out.Close() 116 | //for 'first entry in dir' detection. 117 | dirs := []string{} 118 | err = filepath.Walk(versionDir, func(path string, info os.FileInfo, e error) error { 119 | return ghWalkFunc(path, info, e, outFilename, dirs, tp, format, report) 120 | }) 121 | if err != nil { 122 | return err 123 | } 124 | err = tasks.RunTemplate(reportFilename, templateFile, templateText, out, report, format) 125 | if err != nil { 126 | return err 127 | } 128 | //close explicitly for return value 129 | return out.Close() 130 | } 131 | 132 | func ghWalkFunc(fullPath string, fi2 os.FileInfo, err error, reportFilename string, dirs []string, tp tasks.TaskParams, format string, report tasks.BtReport) error { 133 | excludeResources := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "exclude") 134 | excludeGlobs := core.ParseCommaGlobs(excludeResources) 135 | versionDir := filepath.Join(tp.OutDestRoot, tp.Settings.GetFullVersionName()) 136 | relativePath := strings.Replace(fullPath, versionDir, "", -1) 137 | relativePath = strings.Replace(relativePath, "\\", "/", -1) 138 | relativePath = strings.TrimPrefix(relativePath, "/") 139 | //fmt.Printf("relative path %s, full path %s\n", relativePath, fullPath) 140 | if fi2.IsDir() { 141 | //check globs ... 142 | for _, excludeGlob := range excludeGlobs { 143 | ok, err := filepath.Match(excludeGlob, fi2.Name()) 144 | if err != nil { 145 | return err 146 | } 147 | if ok { 148 | if tp.Settings.IsVerbose() { 149 | log.Printf("Excluded: %s (pattern %v)", relativePath, excludeGlob) 150 | } 151 | return filepath.SkipDir 152 | } 153 | } 154 | return nil 155 | } 156 | if fi2.Name() == reportFilename { 157 | return nil 158 | } 159 | owner := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "owner") 160 | user := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "user") 161 | if user == "" { 162 | user = owner 163 | } 164 | apikey := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "apikey") 165 | repository := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "repository") 166 | apiHost := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "apihost") 167 | //uploadApiHost := "https://uploads.github.com" 168 | downloadsHost := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "downloadshost") 169 | includeResources := tp.Settings.GetTaskSettingString(tasks.TASK_PUBLISH_GITHUB, "include") 170 | 171 | resourceGlobs := core.ParseCommaGlobs(includeResources) 172 | //log.Printf("IncludeGlobs: %v", resourceGlobs) 173 | //log.Printf("ExcludeGlobs: %v", excludeGlobs) 174 | matches := false 175 | for _, resourceGlob := range resourceGlobs { 176 | ok, err := filepath.Match(resourceGlob, fi2.Name()) 177 | if err != nil { 178 | return err 179 | } 180 | if ok { 181 | matches = true 182 | } 183 | } 184 | if matches == false { 185 | if tp.Settings.IsVerbose() { 186 | log.Printf("Not included: %s (pattern %v)", relativePath, includeResources) 187 | } 188 | return nil 189 | } 190 | for _, excludeGlob := range excludeGlobs { 191 | ok, err := filepath.Match(excludeGlob, fi2.Name()) 192 | if err != nil { 193 | return err 194 | } 195 | if ok { 196 | if tp.Settings.IsVerbose() { 197 | log.Printf("Excluded: %s (pattern %v)", relativePath, excludeGlob) 198 | } 199 | return nil 200 | } 201 | } 202 | first := true 203 | 204 | parent := filepath.Dir(relativePath) 205 | //platform := strings.Replace(parent, "_", "/", -1) 206 | //fmt.Fprintf(f, "\n * **%s**:", platform) 207 | for _, d := range dirs { 208 | if d == parent { 209 | first = false 210 | } 211 | } 212 | if first { 213 | dirs = append(dirs, parent) 214 | } 215 | //fmt.Printf("relative path %s, platform %s\n", relativePath, parent) 216 | text := fi2.Name() 217 | version := tp.Settings.GetFullVersionName() 218 | isVerbose := tp.Settings.IsVerbose() 219 | isQuiet := tp.Settings.IsQuiet() 220 | contentType := httpc.GetContentType(text) 221 | 222 | prefix := tp.Settings.GetTaskSettingString(tasks.TASK_TAG, "prefix") 223 | tagName := prefix + version 224 | release, uploadApiHost, err := ghGetReleaseForTag(apiHost, owner, apikey, repository, tagName, isVerbose) 225 | if err != nil { 226 | return err 227 | } 228 | err = ghDoUpload(uploadApiHost, apikey, owner, repository, release, relativePath, fullPath, contentType, isVerbose, isQuiet) 229 | if err != nil { 230 | return err 231 | } 232 | if first { 233 | first = false 234 | } else { 235 | //commaIfRequired = "," 236 | } 237 | if format == "markdown" { 238 | text = strings.Replace(text, "_", "\\_", -1) 239 | } 240 | category := tasks.GetCategory(relativePath) 241 | downloadsUrl := downloadsHost + "/" + owner + "/" + repository + "/releases/download/" + version + "/" + relativePath + "" 242 | download := tasks.BtDownload{Text: text, RelativeLink: downloadsUrl} 243 | v, ok := report.Categories[category] 244 | var existing []tasks.BtDownload 245 | if !ok { 246 | existing = []tasks.BtDownload{} 247 | } else { 248 | existing = *v 249 | } 250 | existing = append(existing, download) 251 | report.Categories[category] = &existing 252 | 253 | return err 254 | } 255 | 256 | func ghGetReleaseForTag(apihost, owner, apikey, repo, tagName string, isVerbose bool) (string, string, error) { 257 | r, err := httpc.DoHttp("GET", apihost+"/repos/"+owner+"/"+repo+"/releases/tags/"+tagName, "", owner, apikey, "", nil, 0, isVerbose) 258 | if err != nil { 259 | return "", "", err 260 | } 261 | i, err := httpc.ParseMap(r, isVerbose) 262 | if err != nil { 263 | return "", "", err 264 | } 265 | var id string 266 | idI, ok := i["id"] 267 | if !ok { 268 | return "", "", fmt.Errorf("Id not provided") 269 | } 270 | switch i := idI.(type) { 271 | case float64: 272 | id = fmt.Sprintf("%0.f", i) 273 | default: 274 | return "", "", fmt.Errorf("ID not a float") 275 | } 276 | 277 | uploadURLi, ok := i["upload_url"] 278 | if !ok { 279 | return "", "", fmt.Errorf("Upload URL not provided") 280 | } 281 | uploadURL := uploadURLi.(string) 282 | uploadURL = strings.Split(uploadURL, "{")[0] 283 | return id, uploadURL, err 284 | } 285 | 286 | //POST https:///repos/:owner/:repo/releases/:id/assets?name=foo.zip 287 | func ghDoUpload(apiHost, apikey, owner, repository, release, relativePath, fullPath, contentType string, isVerbose, isQuiet bool) error { 288 | //POST /repos/:owner/:repo/releases/:id/assets?name=foo.zip 289 | url := apiHost + "?name=" + relativePath 290 | if !isQuiet { 291 | log.Printf("Uploading to %v", url) 292 | } 293 | resp, err := httpc.UploadFile("POST", url, repository, owner, apikey, fullPath, relativePath, contentType, isVerbose) 294 | if err != nil { 295 | if serr, ok := err.(httpc.HttpError); ok { 296 | if serr.StatusCode == 409 || serr.StatusCode == 422 { 297 | //conflict. skip 298 | //continue but dont publish. 299 | //TODO - provide an option to replace existing artifact 300 | //TODO - ?check exists before attempting upload? 301 | log.Printf("WARNING - file already exists. Skipping. %v", resp) 302 | return nil 303 | } else { 304 | return err 305 | } 306 | } else { 307 | return err 308 | } 309 | } 310 | if isVerbose { 311 | log.Printf("File uploaded. Response: %v", resp) 312 | } else if !isQuiet { 313 | log.Printf("File uploaded.") 314 | } 315 | return err 316 | } 317 | 318 | //POST /repos/:owner/:repo/releases 319 | func createRelease(apihost, owner, apikey, repo, tagName, version, body string, preRelease, isVerbose bool) error { 320 | req := map[string]interface{}{"tag_name": tagName, "name": version, "body": body, "prerelease": preRelease} 321 | requestData, err := json.Marshal(req) 322 | if err != nil { 323 | return err 324 | } 325 | requestLength := len(requestData) 326 | reader := bytes.NewReader(requestData) 327 | resp, err := httpc.DoHttp("POST", apihost+"/repos/"+owner+"/"+repo+"/releases", owner, owner, apikey, "", reader, int64(requestLength), isVerbose) 328 | if err == nil { 329 | if isVerbose { 330 | log.Printf("Created new version. %v", resp) 331 | i, err := httpc.ParseMap(resp, isVerbose) 332 | if err != nil { 333 | log.Printf("Error parsing response as map: %v", err) 334 | return err 335 | } 336 | log.Printf("Created new version: %+v", i) 337 | 338 | } 339 | } 340 | return err 341 | } 342 | -------------------------------------------------------------------------------- /tasks/github/github_task.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import "github.com/laher/goxc/tasks" 4 | 5 | //runs automatically 6 | func init() { 7 | tasks.Register(tasks.Task{ 8 | Name: tasks.TASK_PUBLISH_GITHUB, 9 | Description: "Upload artifacts to github.com, and generate a local markdown page of links (github project details required in goxc config. See `goxc -h publish-github`)", 10 | Run: RunTaskPubGH, 11 | DefaultSettings: map[string]interface{}{"owner": "", "apikey": "", "repository": "", 12 | "apihost": "https://api.github.com", 13 | "prerelease": false, 14 | "body": "Built by goxc", 15 | "downloadshost": "https://github.com/", 16 | "downloadspage": "github.md", 17 | "fileheader": "---\nlayout: default\ntitle: Downloads\n---\nFiles hosted at [github.com](https://github.com)\n\n", 18 | "include": "*.zip,*.tar.gz,*.deb", 19 | "exclude": "github.md,.goxc-temp", 20 | "outputFormat": "by-file-extension", // use by-file-extension, markdown or html 21 | "templateText": `--- 22 | layout: default 23 | title: Downloads 24 | --- 25 | Files hosted at [github.com](https://github.com) 26 | 27 | {{.AppName}} downloads (version {{.Version}}) 28 | 29 | {{range $k, $v := .Categories}}### {{$k}} 30 | 31 | {{range $v}} * [{{.Text}}]({{.RelativeLink}}) 32 | {{end}} 33 | {{end}} 34 | 35 | {{.ExtraVars.footer}}`, 36 | "templateFile": "", //use if populated 37 | "templateExtraVars": map[string]interface{}{"footer": "Generated by goxc"}}}) 38 | } 39 | 40 | /* 41 | type GhDownload struct { 42 | Text string 43 | RelativeLink string 44 | } 45 | type GhReport struct { 46 | AppName string 47 | Version string 48 | Categories map[string]*[]GhDownload 49 | ExtraVars map[string]interface{} 50 | } 51 | */ 52 | -------------------------------------------------------------------------------- /tasks/github/github_test.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "testing" 7 | 8 | "github.com/laher/goxc/tasks" 9 | "github.com/laher/goxc/tasks/httpc" 10 | ) 11 | 12 | var apikey = flag.String("api-key", "", "API key") 13 | 14 | var ( 15 | version = "v0.16.0-testx2" 16 | tagName = "v0.16.0-testx" 17 | apihost = "https://api.github.com" 18 | owner = "laher" 19 | repo = "goxc" 20 | isVerbose = true 21 | ) 22 | 23 | func TestCreateRelease(t *testing.T) { 24 | if *apikey == "" { 25 | t.Skip("api-key is required to run this integration test") 26 | } 27 | t.Logf("create release") 28 | err := createRelease(apihost, owner, *apikey, repo, tagName, version, "Built by goxc", true, isVerbose) 29 | if err != nil { 30 | t.Errorf("Error creating release %v", err) 31 | } 32 | } 33 | 34 | ///repos/:owner/:repo/releases/tags/:tag 35 | func TestGetTagRelease(t *testing.T) { 36 | if *apikey == "" { 37 | t.Skip("api-key is required to run this integration test") 38 | } 39 | id, uploadURL, err := ghGetReleaseForTag(apihost, owner, *apikey, repo, tagName, isVerbose) 40 | if err != nil { 41 | t.Errorf("Error getting release %v", err) 42 | } 43 | t.Logf("ID: %s uploadURL: %s", id, uploadURL) 44 | } 45 | 46 | func TestGetReleases(t *testing.T) { 47 | if *apikey == "" { 48 | t.Skip("api-key is required to run this integration test") 49 | } 50 | r, err := httpc.DoHttp("GET", apihost+"/repos/"+owner+"/"+repo+"/releases", "", owner, *apikey, "", nil, 0, isVerbose) 51 | if err != nil { 52 | t.Errorf("Error getting release %v", err) 53 | } 54 | a, err := httpc.ParseSlice(r, isVerbose) 55 | if err != nil { 56 | t.Errorf("Error getting release %v", err) 57 | } 58 | for _, i := range a { 59 | id := i["id"] 60 | name := i["name"] 61 | t.Logf("ID: %0.f Name: %s", id, name) 62 | // for k, v := range i { 63 | // t.Logf("Entry: %s %+v", k, v) 64 | // } 65 | } 66 | //t.Logf("Response data: %v", a) 67 | } 68 | 69 | func TestGhDoUpload(t *testing.T) { 70 | 71 | if *apikey == "" { 72 | t.Skip("api-key is required to run this integration test") 73 | } 74 | t.Logf("do upload") 75 | apihost := "https://uploads.github.com" 76 | version := "v0.16.0-testx" 77 | relativePath := "tasks.go" 78 | fullPath := "tasks.go" 79 | contentType := "text/plain" 80 | err := ghDoUpload(apihost, *apikey, owner, repo, version, relativePath, fullPath, contentType, true, false) 81 | if err != nil { 82 | t.Errorf("Error creating release %v", err) 83 | } 84 | } 85 | 86 | func TestGhWalkFunc(t *testing.T) { 87 | 88 | t.Skip() 89 | fullPath := "" 90 | var fi2 os.FileInfo 91 | var errIn error 92 | reportFilename := "" 93 | dirs := []string{} 94 | var tp tasks.TaskParams 95 | var report tasks.BtReport 96 | format := "" 97 | err := ghWalkFunc(fullPath, fi2, errIn, reportFilename, dirs, tp, format, report) 98 | if err != nil { 99 | 100 | t.Errorf("Error doing walkFunc %v", err) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /tasks/go-clean.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 21 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 22 | "github.com/laher/goxc/executils" 23 | ) 24 | 25 | //runs automatically 26 | func init() { 27 | Register(Task{ 28 | TASK_GO_CLEAN, 29 | "runs `go clean`.", 30 | runTaskGoClean, 31 | nil}) 32 | } 33 | 34 | func runTaskGoClean(tp TaskParams) error { 35 | err := executils.InvokeGo(tp.WorkingDirectory, "clean", []string{}, []string{}, tp.Settings) 36 | return err 37 | } 38 | -------------------------------------------------------------------------------- /tasks/go-fmt.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 21 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 22 | "github.com/laher/goxc/executils" 23 | ) 24 | 25 | //runs automatically 26 | func init() { 27 | Register(Task{ 28 | TASK_GO_FMT, 29 | "runs `go fmt ./...`.", 30 | runTaskGoFmt, 31 | map[string]interface{}{"dir": "./..."}}) 32 | } 33 | 34 | func runTaskGoFmt(tp TaskParams) error { 35 | dir := tp.Settings.GetTaskSettingString(TASK_GO_FMT, "dir") 36 | err := executils.InvokeGo(tp.WorkingDirectory, "fmt", []string{dir}, []string{}, tp.Settings) 37 | return err 38 | } 39 | -------------------------------------------------------------------------------- /tasks/go-install.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 21 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 22 | "github.com/laher/goxc/executils" 23 | ) 24 | 25 | //runs automatically 26 | func init() { 27 | Register(Task{ 28 | TASK_GO_INSTALL, 29 | "runs `go install`. installs a version consistent with goxc-built binaries.", 30 | runTaskGoInstall, 31 | nil}) 32 | } 33 | 34 | func runTaskGoInstall(tp TaskParams) error { 35 | for _, mainDir := range tp.MainDirs { 36 | err := executils.InvokeGo(mainDir, "install", []string{}, []string{}, tp.Settings) 37 | if err != nil { 38 | return err 39 | } 40 | } 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /tasks/go-test.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 21 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 22 | "github.com/laher/goxc/executils" 23 | 24 | "log" 25 | ) 26 | 27 | //runs automatically 28 | func init() { 29 | Register(Task{ 30 | TASK_GO_TEST, 31 | "runs `go test ./...`. (dir is configurable).", 32 | runTaskGoTest, 33 | map[string]interface{}{"dir": "./...", "i": false, "short": false}}) 34 | } 35 | 36 | func runTaskGoTest(tp TaskParams) error { 37 | dir := tp.Settings.GetTaskSettingString(TASK_GO_TEST, "dir") 38 | i := tp.Settings.GetTaskSettingBool(TASK_GO_TEST, "i") //this should be false by default! leaving it exposed for invocation as a flag 39 | short := tp.Settings.GetTaskSettingBool(TASK_GO_TEST, "short") 40 | args := []string{} 41 | if i { 42 | args = append(args, "-i") 43 | } 44 | if short { 45 | args = append(args, "-short") 46 | } 47 | args = append(args, dir) 48 | if tp.Settings.IsVerbose() { 49 | log.Printf("Running `go test` with args: %v", args) 50 | } 51 | err := executils.InvokeGo(tp.WorkingDirectory, "test", args, []string{}, tp.Settings) 52 | return err 53 | } 54 | -------------------------------------------------------------------------------- /tasks/go-vet.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 21 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 22 | "log" 23 | 24 | "github.com/laher/goxc/executils" 25 | ) 26 | 27 | //runs automatically 28 | func init() { 29 | Register(Task{ 30 | TASK_GO_VET, 31 | "runs `go vet ./...`.", 32 | runTaskGoVet, 33 | map[string]interface{}{"dir": "./..."}}) 34 | } 35 | 36 | func runTaskGoVet(tp TaskParams) error { 37 | dir := tp.Settings.GetTaskSettingString(TASK_GO_VET, "dir") 38 | args := []string{dir} 39 | err := executils.InvokeGo(tp.WorkingDirectory, "vet", args, []string{}, tp.Settings) 40 | //v0.8.3 treat this as a warning only. 41 | if err != nil { 42 | log.Print("Go-vet failed (goxc just treats this as a warning for now)") 43 | } 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /tasks/http.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | import ( 19 | "bytes" 20 | "fmt" 21 | "text/template" 22 | //Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 23 | //see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 24 | "github.com/laher/goxc/core" 25 | "github.com/laher/goxc/tasks/httpc" 26 | //"github.com/laher/goxc/typeutils" 27 | "log" 28 | "os" 29 | "path/filepath" 30 | "strings" 31 | ) 32 | 33 | // TASK_PUBLISH_HTTP is the task name to be used in the CLI 34 | const TASK_PUBLISH_HTTP = "publish-http" 35 | 36 | type httpTaskConfig struct { 37 | includePatterns string 38 | excludePatterns string 39 | username string 40 | password string 41 | exists string 42 | urlTemplate *template.Template 43 | } 44 | 45 | func init() { 46 | Register(Task{TASK_PUBLISH_HTTP, 47 | "Upload artifacts to an HTTP server using a PUT request. Configuration required, see `goxc -h http` output.", 48 | httpRunTask, 49 | map[string]interface{}{ 50 | "url-template": "", 51 | "username": "", 52 | "password": "", 53 | "include": "*.zip,*.tar.gz,*.deb", 54 | "exclude": "*.orig.tar.gz,data.tar.gz,control.tar.gz,*.debian.tar.gz,*-dev_*.deb", 55 | "exists-action": "fail", 56 | }}) 57 | } 58 | 59 | func httpRunTask(tp TaskParams) error { 60 | urlTemplateString := tp.Settings.GetTaskSettingString(TASK_PUBLISH_HTTP, "url-template") 61 | missing := []string{} 62 | if urlTemplateString == "" { 63 | missing = append(missing, "url-template") 64 | } 65 | if len(missing) > 0 { 66 | return fmt.Errorf("HTTP task configuration missing %v", missing) 67 | } 68 | template := template.New("url-template") 69 | if _, err := template.Parse(urlTemplateString); err != nil { 70 | return err 71 | } 72 | config := &httpTaskConfig{ 73 | exists: strings.ToLower(tp.Settings.GetTaskSettingString(TASK_PUBLISH_HTTP, "exists-action")), 74 | includePatterns: tp.Settings.GetTaskSettingString(TASK_PUBLISH_HTTP, "include"), 75 | excludePatterns: tp.Settings.GetTaskSettingString(TASK_PUBLISH_HTTP, "exclude"), 76 | username: tp.Settings.GetTaskSettingString(TASK_PUBLISH_HTTP, "username"), 77 | password: tp.Settings.GetTaskSettingString(TASK_PUBLISH_HTTP, "password"), 78 | urlTemplate: template, 79 | } 80 | versionDir := filepath.Join(tp.OutDestRoot, tp.Settings.GetFullVersionName()) 81 | err := filepath.Walk(versionDir, func(path string, info os.FileInfo, e error) error { 82 | return httpWalk(config, path, info, e, tp) 83 | }) 84 | if err != nil { 85 | return err 86 | } 87 | return nil 88 | } 89 | 90 | func httpWalk(config *httpTaskConfig, fullPath string, fi os.FileInfo, err error, tp TaskParams) error { 91 | if err != nil { 92 | return err 93 | } 94 | if fi.IsDir() { 95 | return nil 96 | } 97 | versionDir := filepath.Join(tp.OutDestRoot, tp.Settings.GetFullVersionName()) 98 | relativePath := strings.Replace(fullPath, versionDir, "", -1) 99 | relativePath = strings.Replace(relativePath, "\\", "/", -1) 100 | relativePath = strings.TrimPrefix(relativePath, "/") 101 | if !tp.Settings.IsQuiet() { 102 | log.Printf("Considering %s", relativePath) 103 | } 104 | resourceGlobs := core.ParseCommaGlobs(config.includePatterns) 105 | excludeGlobs := core.ParseCommaGlobs(config.excludePatterns) 106 | matches := false 107 | for _, resourceGlob := range resourceGlobs { 108 | ok, err := filepath.Match(resourceGlob, fi.Name()) 109 | if err != nil { 110 | return err 111 | } 112 | if ok { 113 | matches = true 114 | } 115 | } 116 | if matches == false { 117 | if !tp.Settings.IsQuiet() { 118 | log.Printf("Not including %s (for include patterns %s)", relativePath, strings.Join(resourceGlobs, ", ")) 119 | } 120 | return nil 121 | } 122 | for _, excludeGlob := range excludeGlobs { 123 | ok, err := filepath.Match(excludeGlob, fi.Name()) 124 | if err != nil { 125 | return err 126 | } 127 | if ok { 128 | if !tp.Settings.IsQuiet() { 129 | log.Printf("Excluding %s (for exclude pattern %s)", relativePath, excludeGlob) 130 | } 131 | return nil 132 | } 133 | } 134 | return httpUploadFile(config, fullPath, fi, tp) 135 | } 136 | 137 | func httpURLTemplateContext(tp TaskParams, fi os.FileInfo) map[string]interface{} { 138 | return map[string]interface{}{ 139 | "AppName": tp.Settings.AppName, 140 | "Version": tp.Settings.GetFullVersionName(), 141 | "Arch": tp.Settings.Arch, 142 | "Os": tp.Settings.Os, 143 | "PackageVersion": tp.Settings.PackageVersion, 144 | "BranchName": tp.Settings.BranchName, 145 | "PrereleaseInfo": tp.Settings.PrereleaseInfo, 146 | "BuildName": tp.Settings.BuildName, 147 | "FormatVersion": tp.Settings.FormatVersion, 148 | "FileName": fi.Name(), 149 | "FileSize": fi.Size(), 150 | "FileModTime": fi.ModTime(), 151 | "FileMode": fi.Mode(), 152 | } 153 | } 154 | 155 | func httpExistsFile(config *httpTaskConfig, url string) (bool, error) { 156 | res, err := httpc.DoHttp("HEAD", url, "", config.username, config.password, "", nil, 0, true) 157 | if err != nil { 158 | return false, err 159 | } 160 | switch res.StatusCode { 161 | case 200: 162 | return true, nil 163 | case 404: 164 | return false, nil 165 | default: 166 | return false, fmt.Errorf("Unexpected status code %v when testing for existence of %v", res.StatusCode, url) 167 | } 168 | } 169 | 170 | func httpDeleteFile(config *httpTaskConfig, url string) error { 171 | res, err := httpc.DoHttp("DELETE", url, "", config.username, config.password, "", nil, 0, true) 172 | if err != nil { 173 | return err 174 | } 175 | if res.StatusCode/100 != 2 { 176 | return fmt.Errorf("Unable to delete %v: %v", url, res.Status) 177 | } 178 | return nil 179 | } 180 | 181 | func httpUploadFile(config *httpTaskConfig, fullPath string, fi os.FileInfo, tp TaskParams) error { 182 | var urlb bytes.Buffer 183 | err := config.urlTemplate.Execute(&urlb, httpURLTemplateContext(tp, fi)) 184 | if err != nil { 185 | return err 186 | } 187 | var url = urlb.String() 188 | exists, err := httpExistsFile(config, url) 189 | if err != nil { 190 | return err 191 | } 192 | if exists { 193 | switch config.exists { 194 | case "replace": 195 | if !tp.Settings.IsQuiet() { 196 | log.Printf("Deleting existent file %v at %v", fi.Name(), url) 197 | } 198 | if err := httpDeleteFile(config, url); err != nil { 199 | return err 200 | } 201 | case "omit": 202 | if !tp.Settings.IsQuiet() { 203 | log.Printf("Omitting existent file %v at %v", fi.Name(), url) 204 | } 205 | return nil 206 | case "fail": 207 | return fmt.Errorf("Failing http publish of %v because it already exists at %v", fi.Name(), url) 208 | } 209 | } 210 | if !tp.Settings.IsQuiet() { 211 | log.Printf("Putting %s to %s", fi.Name(), url) 212 | } 213 | b, err := os.Open(fullPath) 214 | if err != nil { 215 | return err 216 | } 217 | res, err := httpc.DoHttp("PUT", url, "", config.username, config.password, "application/octet-stream", b, fi.Size(), true) 218 | if err != nil { 219 | return err 220 | } 221 | if res.StatusCode/100 != 2 { 222 | return fmt.Errorf("Unexpected response: %v", res.Status) 223 | } 224 | return nil 225 | } 226 | -------------------------------------------------------------------------------- /tasks/httpc/http.go: -------------------------------------------------------------------------------- 1 | package httpc 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "os" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | type HttpError struct { 16 | StatusCode int 17 | message string 18 | } 19 | 20 | func (e HttpError) Error() string { 21 | return fmt.Sprintf("Error code: %d, message: %s", e.StatusCode, e.message) 22 | } 23 | 24 | func DoHttp(method, url, _deprecated, user, apikey, contentType string, requestReader io.Reader, requestLength int64, isVerbose bool) (*http.Response, error) { 25 | client := &http.Client{} 26 | req, err := http.NewRequest(method, url, requestReader) 27 | if err != nil { 28 | return nil, err 29 | } 30 | req.SetBasicAuth(user, apikey) 31 | if requestLength > 0 { 32 | if isVerbose { 33 | log.Printf("Adding Header - Content-Length: %s", strconv.FormatInt(requestLength, 10)) 34 | } 35 | req.ContentLength = requestLength 36 | } 37 | if contentType != "" { 38 | if isVerbose { 39 | log.Printf("Adding Header - Content-Type: %s", contentType) 40 | } 41 | 42 | req.Header.Add("Content-Type", contentType) 43 | } 44 | //log.Printf("req: %v", req) 45 | if isVerbose { 46 | log.Printf("%s to %s", method, url) 47 | } 48 | resp, err := client.Do(req) 49 | if err != nil { 50 | return nil, err 51 | } 52 | if isVerbose { 53 | log.Printf("Http response received") 54 | } 55 | 56 | return resp, nil 57 | } 58 | 59 | func ParseSlice(resp *http.Response, isVerbose bool) ([]map[string]interface{}, error) { 60 | body, err := ioutil.ReadAll(resp.Body) 61 | if err != nil { 62 | return nil, err 63 | } 64 | err = resp.Body.Close() 65 | if err != nil { 66 | log.Printf("Error closing response body: %v", err) 67 | } 68 | //200 is OK, 201 is Created, etc 69 | if resp.StatusCode < 200 || resp.StatusCode > 299 { 70 | log.Printf("Error code: %s", resp.Status) 71 | log.Printf("Error body: %s", body) 72 | return nil, HttpError{resp.StatusCode, resp.Status} 73 | } 74 | if isVerbose { 75 | log.Printf("Response status: '%s', Body: %s", resp.Status, body) 76 | } 77 | var b []map[string]interface{} 78 | if len(body) > 0 { 79 | err = json.Unmarshal(body, &b) 80 | if err != nil { 81 | return nil, err 82 | } 83 | } 84 | return b, err 85 | } 86 | 87 | func ParseMap(resp *http.Response, isVerbose bool) (map[string]interface{}, error) { 88 | body, err := ioutil.ReadAll(resp.Body) 89 | if err != nil { 90 | return nil, err 91 | } 92 | err = resp.Body.Close() 93 | if err != nil { 94 | log.Printf("Error closing response body: %v", err) 95 | } 96 | //200 is OK, 201 is Created, etc 97 | if resp.StatusCode < 200 || resp.StatusCode > 299 { 98 | log.Printf("Error code: %s", resp.Status) 99 | log.Printf("Error body: %s", body) 100 | return nil, HttpError{resp.StatusCode, resp.Status} 101 | } 102 | if isVerbose { 103 | log.Printf("Response status: '%s', Body: %s", resp.Status, body) 104 | } 105 | var b map[string]interface{} 106 | if len(body) > 0 { 107 | err = json.Unmarshal(body, &b) 108 | if err != nil { 109 | return nil, err 110 | } 111 | } 112 | return b, err 113 | } 114 | 115 | func GetContentType(text string) string { 116 | contentType := "text/plain" 117 | if strings.HasSuffix(text, ".zip") { 118 | contentType = "application/zip" 119 | } else if strings.HasSuffix(text, ".deb") { 120 | contentType = "application/vnd.debian.binary-package" 121 | } else if strings.HasSuffix(text, ".tar.gz") { 122 | contentType = "application/x-gzip" 123 | } 124 | return contentType 125 | } 126 | 127 | func UploadFile(method, url, subject, user, apikey, fullPath, relativePath, contentType string, isVerbose bool) (map[string]interface{}, error) { 128 | file, err := os.Open(fullPath) 129 | if err != nil { 130 | log.Printf("Error reading file for upload: %v", err) 131 | return nil, err 132 | } 133 | defer file.Close() 134 | fi, err := file.Stat() 135 | if err != nil { 136 | log.Printf("Error statting file for upload: %v", err) 137 | return nil, err 138 | } 139 | resp, err := DoHttp(method, url, subject, user, apikey, contentType, file, fi.Size(), isVerbose) 140 | r, err := ParseMap(resp, isVerbose) 141 | return r, err 142 | } 143 | -------------------------------------------------------------------------------- /tasks/interpolate-source.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "fmt" 21 | "go/parser" 22 | "go/printer" 23 | "go/token" 24 | "log" 25 | "os" 26 | "path/filepath" 27 | "time" 28 | 29 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 30 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 31 | "github.com/laher/goxc/source" 32 | ) 33 | 34 | const ( 35 | TASK_INTERPOLATE_SOURCE = "interpolate-source" 36 | ) 37 | 38 | //runs automatically 39 | func init() { 40 | Register(Task{ 41 | TASK_INTERPOLATE_SOURCE, 42 | "Replaces a given constant/var value with the current version.", 43 | runTaskInterpolateSource, 44 | map[string]interface{}{"varnameVersion": "VERSION", "varnameSourceDate": "SOURCE_DATE"}}) 45 | } 46 | 47 | func runTaskInterpolateSource(tp TaskParams) error { 48 | err := writeSource(tp) 49 | return err 50 | } 51 | 52 | func writeSource(tp TaskParams) error { 53 | 54 | varnameVersion := tp.Settings.GetTaskSettingString(TASK_INTERPOLATE_SOURCE, "varnameVersion") 55 | versionName := tp.Settings.GetFullVersionName() 56 | err := writeVar(tp, varnameVersion, versionName) 57 | if err != nil { 58 | return err 59 | } 60 | varnameSourceDate := tp.Settings.GetTaskSettingString(TASK_INTERPOLATE_SOURCE, "varnameSourceDate") 61 | sourceDate := time.Now().Format(time.RFC3339) 62 | return writeVar(tp, varnameSourceDate, sourceDate) 63 | } 64 | 65 | func writeVar(tp TaskParams, varname, varval string) (err error) { 66 | if varname != "" { 67 | matches, err := filepath.Glob(filepath.Join(tp.WorkingDirectory, "**.go")) 68 | if err != nil { 69 | return err 70 | } 71 | if tp.Settings.IsVerbose() { 72 | log.Printf("Source files: %v", matches) 73 | } 74 | fset := token.NewFileSet() // positions are relative to fset 75 | found := false 76 | for _, match := range matches { 77 | f, err := parser.ParseFile(fset, match, nil, parser.ParseComments) 78 | if err != nil { 79 | return err 80 | } 81 | //find version var 82 | versionVar := source.FindValue(f, varname, []token.Token{token.CONST, token.VAR}, tp.Settings.IsVerbose()) 83 | if versionVar != nil { 84 | found = true 85 | varvalQuoted := fmt.Sprintf("\"%s\"", varval) 86 | if !tp.Settings.IsQuiet() { 87 | log.Printf("Changing source of '%s' = %v -> %s", varname, versionVar.Value, varvalQuoted) 88 | } 89 | versionVar.Value = varvalQuoted 90 | fw, err := os.OpenFile(match, os.O_WRONLY|os.O_TRUNC, 0644) 91 | if err != nil { 92 | return err 93 | } 94 | defer fw.Close() 95 | err = (&printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}).Fprint(fw, fset, f) 96 | if err != nil { 97 | return err 98 | } 99 | err = fw.Close() 100 | if err != nil { 101 | return err 102 | } 103 | } 104 | } 105 | if !found { 106 | log.Printf("Version var '%s' not found", varname) 107 | } 108 | 109 | } 110 | if err != nil { 111 | return err 112 | } 113 | return err 114 | } 115 | -------------------------------------------------------------------------------- /tasks/rice-append.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os/exec" 7 | "path/filepath" 8 | 9 | "github.com/laher/goxc/config" 10 | "github.com/laher/goxc/core" 11 | "github.com/laher/goxc/executils" 12 | ) 13 | 14 | const riceNotFound = `Could not find 'rice' executable on $PATH. 15 | Please ensure you have installed it: 16 | 17 | go get -u github.com/GeertJohan/go.rice/rice 18 | 19 | Error: %v` 20 | 21 | var riceAppendTask = Task{ 22 | TASK_RICE_APPEND, 23 | "Append embedded resources to the binary using go.rice.", 24 | runTaskRiceAppend, 25 | map[string]interface{}{ 26 | "import-paths": []string{}, 27 | }} 28 | 29 | //runs automatically 30 | func init() { 31 | Register(riceAppendTask) 32 | } 33 | 34 | func runTaskRiceAppend(tp TaskParams) (err error) { 35 | ricePath, err := exec.LookPath("rice") 36 | if err != nil { 37 | return fmt.Errorf(riceNotFound, err) 38 | } 39 | for _, dest := range tp.DestPlatforms { 40 | for _, mainDir := range tp.MainDirs { 41 | var exeName string 42 | if len(tp.MainDirs) == 1 { 43 | exeName = tp.Settings.AppName 44 | } else { 45 | exeName = filepath.Base(mainDir) 46 | 47 | } 48 | binPath, err := core.GetAbsoluteBin(dest.Os, dest.Arch, tp.Settings.AppName, exeName, tp.WorkingDirectory, tp.Settings.GetFullVersionName(), tp.Settings.OutPath, tp.Settings.ArtifactsDest) 49 | 50 | if err != nil { 51 | return err 52 | } 53 | if err = riceAppendPlat(dest.Os, dest.Arch, binPath, ricePath, tp.Settings); err != nil { 54 | return err 55 | } 56 | } 57 | } 58 | return nil 59 | } 60 | 61 | func riceAppendPlat(goos, arch string, binPath string, ricePath string, settings *config.Settings) error { 62 | importPaths := settings.GetTaskSettingStringSlice("rice-append", "import-paths") 63 | if err := riceAppend(binPath, ricePath, importPaths); err != nil { 64 | log.Printf("rice-append failed for %s: %s", binPath, err) 65 | return err 66 | } 67 | if !settings.IsQuiet() { 68 | log.Printf("rice-append successful for: %s", binPath) 69 | } 70 | return nil 71 | } 72 | 73 | func riceAppend(binPath string, ricePath string, importPaths []string) error { 74 | cmd := exec.Command(ricePath) 75 | cmd.Args = append(cmd.Args, "append", fmt.Sprintf("--exec=%s", binPath)) 76 | for _, importPath := range importPaths { 77 | cmd.Args = append(cmd.Args, fmt.Sprintf("--import-path=%s", importPath)) 78 | } 79 | 80 | return executils.StartAndWait(cmd) 81 | } 82 | -------------------------------------------------------------------------------- /tasks/rmbin.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "io/ioutil" 21 | "log" 22 | "os" 23 | "path/filepath" 24 | 25 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 26 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 27 | "github.com/laher/goxc/core" 28 | "github.com/laher/goxc/platforms" 29 | ) 30 | 31 | //runs automatically 32 | func init() { 33 | Register(Task{ 34 | "rmbin", 35 | "delete binary. Normally runs after 'archive' task to reduce size of output dir.", 36 | runTaskRmBin, 37 | nil}) 38 | } 39 | 40 | func runTaskRmBin(tp TaskParams) error { 41 | for _, dest := range tp.DestPlatforms { 42 | for _, mainDir := range tp.MainDirs { 43 | var exeName string 44 | if len(tp.MainDirs) == 1 { 45 | exeName = tp.Settings.AppName 46 | } else { 47 | exeName = filepath.Base(mainDir) 48 | 49 | } 50 | err := rmBinPlat(dest, tp, exeName) 51 | if err != nil { 52 | //todo - add a force option? 53 | log.Printf("%v", err) 54 | } 55 | } 56 | } 57 | //TODO return error 58 | return nil 59 | } 60 | 61 | func rmBinPlat(dest platforms.Platform, tp TaskParams, exeName string) error { 62 | binPath, err := core.GetAbsoluteBin(dest.Os, dest.Arch, tp.Settings.AppName, exeName, tp.WorkingDirectory, tp.Settings.GetFullVersionName(), tp.Settings.OutPath, tp.Settings.ArtifactsDest) 63 | if err != nil { 64 | return err 65 | } 66 | err = os.Remove(binPath) 67 | if err != nil { 68 | return err 69 | } 70 | //if empty, remove dir 71 | binDir := filepath.Dir(binPath) 72 | files, err := ioutil.ReadDir(binDir) 73 | if err != nil { 74 | return err 75 | } 76 | if len(files) < 1 { 77 | err = os.Remove(binDir) 78 | } 79 | return err 80 | } 81 | -------------------------------------------------------------------------------- /tasks/tag.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "errors" 21 | "os/exec" 22 | 23 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 24 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 25 | "github.com/laher/goxc/executils" 26 | ) 27 | 28 | const TASK_TAG = "tag" 29 | 30 | //runs automatically 31 | func init() { 32 | Register(Task{ 33 | TASK_TAG, 34 | "tag repository according to package version.", 35 | tag, 36 | map[string]interface{}{ 37 | "vcs": "git", "prefix": "v"}}) //TODO support other vcs's 38 | } 39 | 40 | func tag(tp TaskParams) error { 41 | vcs := tp.Settings.GetTaskSettingString(TASK_TAG, "vcs") 42 | prefix := tp.Settings.GetTaskSettingString(TASK_TAG, "prefix") 43 | 44 | if vcs == "git" { 45 | version := tp.Settings.GetFullVersionName() 46 | cmd := exec.Command("git") 47 | args := []string{"tag", prefix + version} 48 | err := executils.PrepareCmd(cmd, tp.WorkingDirectory, args, []string{}, tp.Settings.IsVerbose()) 49 | if err != nil { 50 | return err 51 | } 52 | return executils.StartAndWait(cmd) 53 | } else { 54 | return errors.New("Only 'git' is supported at this stage") 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /tasks/tasks_test.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRegister(t *testing.T) { 8 | l := len(allTasks) 9 | Register(Task{"blah", "blah", nil, nil}) 10 | if len(allTasks)-l != 1 { 11 | t.Fatalf("unexpected result %v should be one more than %v", len(allTasks), l) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tasks/toolchain.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "errors" 21 | "log" 22 | "os" 23 | "os/exec" 24 | "os/signal" 25 | "path/filepath" 26 | 27 | "github.com/laher/goxc/config" 28 | "github.com/laher/goxc/core" 29 | "github.com/laher/goxc/executils" 30 | "github.com/laher/goxc/platforms" 31 | ) 32 | 33 | //runs automatically 34 | func init() { 35 | Register(Task{ 36 | "toolchain", 37 | "Build toolchain. Make sure to run this each time you update go source.", 38 | runTaskToolchain, 39 | map[string]interface{}{"GOARM": "", "extra-env": []string{}, "no-clean": true}}) 40 | } 41 | 42 | func runTaskToolchain(tp TaskParams) error { 43 | if len(tp.DestPlatforms) < 1 { 44 | return errors.New("No valid platforms specified") 45 | } else { 46 | log.Printf("Please do NOT try to quit during a build-toolchain. This can leave your Go toolchain in a non-working state.") 47 | busy := false 48 | schan := make(chan os.Signal, 1) 49 | signal.Notify(schan, os.Interrupt) 50 | g := func() { 51 | for sig := range schan { 52 | // sig is a ^C, handle it 53 | if busy == true { 54 | log.Printf("WARNING!!! Received SIGINT (%v) during buildToolchain! DO NOT QUIT DURING BUILD TOOLCHAIN! You may need to run $GOROOT/src/make.bash (or .bat)", sig) 55 | } 56 | } 57 | } 58 | go g() 59 | success := 0 60 | var err error 61 | for _, dest := range tp.DestPlatforms { 62 | busy = true 63 | err = buildToolchain(dest.Os, dest.Arch, tp.Settings) 64 | if err != nil { 65 | log.Printf("Error: %v", err) 66 | } else { 67 | busy = false 68 | success = success + 1 69 | } 70 | } 71 | if success < 1 { 72 | log.Printf("No successes!") 73 | log.Printf("Have you installed Go from source?? If not, please see http://golang.org/doc/install/source") 74 | return err 75 | } 76 | } 77 | return nil 78 | } 79 | 80 | // Build toolchain for a given target platform 81 | func buildToolchain(goos string, arch string, settings *config.Settings) error { 82 | goroot := settings.GoRoot 83 | scriptpath := core.GetMakeScriptPath(goroot) 84 | cmd := exec.Command(scriptpath) 85 | 86 | cmd.Dir = filepath.Join(goroot, "src") 87 | 88 | noClean := settings.GetTaskSettingBool(TASK_BUILD_TOOLCHAIN, "no-clean") 89 | if noClean { 90 | cmd.Args = append(cmd.Args, "--no-clean") 91 | } 92 | //0.8.5: no longer using cgoEnabled 93 | env := []string{"GOOS=" + goos, "GOARCH=" + arch} 94 | extraEnv := settings.GetTaskSettingStringSlice(TASK_BUILD_TOOLCHAIN, "extra-env") 95 | if settings.IsVerbose() { 96 | log.Printf("extra-env: %v", extraEnv) 97 | } 98 | env = append(env, extraEnv...) 99 | if goos == platforms.LINUX && arch == platforms.ARM { 100 | // see http://dave.cheney.net/2012/09/08/an-introduction-to-cross-compilation-with-go 101 | //NOTE: I don't think it has any effect on fp 102 | goarm := settings.GetTaskSettingString(TASK_BUILD_TOOLCHAIN, "GOARM") 103 | if goarm != "" { 104 | env = append(env, "GOARM="+goarm) 105 | } 106 | } 107 | 108 | if settings.IsVerbose() { 109 | log.Printf("Setting env: %v", env) 110 | } 111 | cmd.Env = append([]string{}, os.Environ()...) 112 | cmd.Env = append(cmd.Env, env...) 113 | if settings.IsVerbose() { 114 | log.Printf("'make' env: GOOS=%s GOARCH=%s GOROOT=%s", goos, arch, goroot) 115 | log.Printf("Invoking '%v' from %s", executils.PrintableArgs(cmd.Args), cmd.Dir) 116 | } 117 | executils.RedirectIO(cmd) 118 | err := executils.StartAndWait(cmd) 119 | if err != nil { 120 | log.Printf("Build toolchain: %s", err) 121 | } 122 | if settings.IsVerbose() { 123 | log.Printf("Complete") 124 | } 125 | return err 126 | } 127 | -------------------------------------------------------------------------------- /tasks/xc.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "errors" 21 | "io" 22 | "log" 23 | "os" 24 | "os/exec" 25 | "path/filepath" 26 | "runtime" 27 | "strings" 28 | 29 | // Tip for Forkers: please 'clone' from my url and then 'pull' from your url. That way you wont need to change the import path. 30 | // see https://groups.google.com/forum/?fromgroups=#!starred/golang-nuts/CY7o2aVNGZY 31 | "github.com/laher/argo/ar" 32 | "github.com/laher/goxc/core" 33 | "github.com/laher/goxc/executils" 34 | "github.com/laher/goxc/exefileparse" 35 | "github.com/laher/goxc/platforms" 36 | ) 37 | 38 | //runs automatically 39 | func init() { 40 | //GOARM=6 (this is the default for go1.1 41 | RegisterParallelizable(ParallelizableTask{ 42 | TASK_XC, 43 | "Cross compile. Builds executables for other platforms.", 44 | setupXc, 45 | runXc, 46 | nil, 47 | map[string]interface{}{"GOARM": "", 48 | //"validation" : "tcBinExists,exeParse", 49 | "validateToolchain": false, 50 | "verifyExe": false, 51 | "autoRebuildToolchain": false}}) 52 | } 53 | 54 | func setupXc(tp TaskParams) ([]platforms.Platform, error) { 55 | 56 | if len(tp.DestPlatforms) == 0 { 57 | return []platforms.Platform{}, errors.New("No valid platforms specified") 58 | } 59 | 60 | isValidateToolchain := tp.Settings.GetTaskSettingBool(TASK_XC, "validateToolchain") 61 | goroot := tp.Settings.GoRoot 62 | for _, dest := range tp.DestPlatforms { 63 | if isValidateToolchain { 64 | err := validateToolchain(dest, goroot, tp.Settings.IsVerbose()) 65 | if err != nil { 66 | log.Printf("Toolchain not ready for %v. Re-building toolchain. (%v)", dest, err) 67 | isAutoToolchain := tp.Settings.GetTaskSettingBool(TASK_XC, "autoRebuildToolchain") 68 | if isAutoToolchain { 69 | err = buildToolchain(dest.Os, dest.Arch, tp.Settings) 70 | } 71 | if err != nil { 72 | return nil, err 73 | } 74 | } 75 | } 76 | } 77 | //check for duplicate exePaths 78 | exePaths := []string{} 79 | for _, mainDir := range tp.MainDirs { 80 | var exeName string 81 | if len(tp.MainDirs) == 1 { 82 | exeName = tp.Settings.AppName 83 | } else { 84 | exeName = filepath.Base(mainDir) 85 | } 86 | for _, dest := range tp.DestPlatforms { 87 | absoluteBin, err := core.GetAbsoluteBin(dest.Os, dest.Arch, tp.Settings.AppName, exeName, tp.WorkingDirectory, tp.Settings.GetFullVersionName(), tp.Settings.OutPath, tp.Settings.ArtifactsDest) 88 | if err != nil { 89 | return nil, err 90 | } 91 | for _, existingPath := range exePaths { 92 | if existingPath == absoluteBin { 93 | return []platforms.Platform{}, errors.New("The xc task will attempt to compile multiple binaries to the same path (" + absoluteBin + "). Please make sure {{.Os}} and {{.Arch}} variables are used in the OutPath. Currently the template is " + tp.Settings.OutPath) 94 | } 95 | } 96 | exePaths = append(exePaths, absoluteBin) 97 | } 98 | } 99 | return tp.DestPlatforms, nil 100 | } 101 | 102 | func runXc(tp TaskParams, dest platforms.Platform, errchan chan error) { 103 | /* 104 | //outDestRoot, err := core.GetOutDestRoot(tp.AppName, tp.WorkingDirectory, tp.Settings.ArtifactsDest) 105 | if err != nil { 106 | log.Printf("Error: %v", err) 107 | errchan <- err 108 | return 109 | } 110 | */ 111 | if tp.Settings.IsVerbose() { 112 | log.Printf("mainDirs : %v", tp.MainDirs) 113 | } 114 | for _, mainDir := range tp.MainDirs { 115 | var exeName string 116 | var packagePath string 117 | if len(tp.MainDirs) == 1 { 118 | exeName = tp.Settings.AppName 119 | } else { 120 | exeName = filepath.Base(mainDir) 121 | } 122 | packagePath = mainDir 123 | absoluteBin, err := xcPlat(dest, tp, exeName, packagePath) 124 | if err != nil { 125 | log.Printf("Error: %v", err) 126 | log.Printf("Have you run `goxc -t` for this platform (%s,%s)???", dest.Arch, dest.Os) 127 | errchan <- err 128 | return 129 | } else { 130 | isVerifyExe := tp.Settings.GetTaskSettingBool(TASK_XC, "verifyExe") 131 | if isVerifyExe { 132 | err = exefileparse.Test(absoluteBin, dest.Arch, dest.Os, tp.Settings.IsVerbose()) 133 | if err != nil { 134 | log.Printf("Error: %v", err) 135 | log.Printf("Something fishy is going on: have you run `goxc -t` for this platform (%s,%s)???", dest.Arch, dest.Os) 136 | errchan <- err 137 | return 138 | } 139 | } 140 | } 141 | } 142 | errchan <- nil 143 | } 144 | 145 | func validateToolchain(dest platforms.Platform, goroot string, verbose bool) error { 146 | err := validatePlatToolchainBinExists(dest, goroot) 147 | if err != nil { 148 | return err 149 | } 150 | err = validatePlatToolchainPackageVersion(dest, goroot, verbose) 151 | if err != nil { 152 | return err 153 | } 154 | 155 | return nil 156 | } 157 | 158 | func validatePlatToolchainPackageVersion(dest platforms.Platform, goroot string, verbose bool) error { 159 | platPkgFileRuntime := filepath.Join(goroot, "pkg", dest.Os+"_"+dest.Arch, "runtime.a") 160 | nr, err := os.Open(platPkgFileRuntime) 161 | if err != nil { 162 | log.Printf("Could not validate toolchain version: %v", err) 163 | } 164 | tr, err := ar.NewReader(nr) 165 | if err != nil { 166 | log.Printf("Could not validate toolchain version: %v", err) 167 | } 168 | for { 169 | h, err := tr.Next() 170 | if err != nil { 171 | if err == io.EOF { 172 | log.Printf("Could not validate toolchain version: %v", err) 173 | return nil 174 | } 175 | log.Printf("Could not validate toolchain version: %v", err) 176 | return err 177 | } 178 | //log.Printf("Header: %+v", h) 179 | if h.Name == "__.PKGDEF" { 180 | firstLine, err := tr.NextString(50) 181 | if err != nil { 182 | log.Printf("failed to read first line of PKGDEF: %v", err) 183 | return nil 184 | } 185 | //log.Printf("pkgdef first part: '%s'", firstLine) 186 | expectedPrefix := "go object " + dest.Os + " " + dest.Arch + " " 187 | if !strings.HasPrefix(firstLine, expectedPrefix) { 188 | log.Printf("first line of __.PKGDEF does not match expected pattern: %v", expectedPrefix) 189 | return nil 190 | } 191 | parts := strings.Split(firstLine, " ") 192 | compiledVersion := parts[4] 193 | //runtimeVersion := runtime.Version() 194 | //log.Printf("Runtime version: %s", runtimeVersion) 195 | cmdPath := filepath.Join(goroot, "bin", "go") 196 | cmd := exec.Command(cmdPath) 197 | args := []string{"version"} 198 | err = executils.PrepareCmd(cmd, ".", args, []string{}, false) 199 | if err != nil { 200 | log.Printf("`go version` failed: %v", err) 201 | return nil 202 | } 203 | goVersionOutput, err := cmd.Output() 204 | if err != nil { 205 | log.Printf("`go version` failed: %v", err) 206 | return nil 207 | } 208 | //log.Printf("output: %s", string(out)) 209 | goVersionOutputParts := strings.Split(string(goVersionOutput), " ") 210 | goVersion := goVersionOutputParts[2] 211 | if compiledVersion != goVersion { 212 | return errors.New("static library version '" + compiledVersion + "' does NOT match `go version` '" + goVersion + "'!") 213 | } 214 | if verbose { 215 | log.Printf("Toolchain version '%s' verified against 'go %s' for %v", compiledVersion, goVersion, dest) 216 | } 217 | return nil 218 | } 219 | } 220 | } 221 | 222 | func validatePlatToolchainBinExists(dest platforms.Platform, goroot string) error { 223 | platGoBin := filepath.Join(goroot, "bin", dest.Os+"_"+dest.Arch, "go") 224 | if dest.Os == runtime.GOOS && dest.Arch == runtime.GOARCH { 225 | 226 | platGoBin = filepath.Join(goroot, "bin", "go") 227 | } 228 | if dest.Os == platforms.WINDOWS { 229 | platGoBin += ".exe" 230 | } 231 | _, err := os.Stat(platGoBin) 232 | return err 233 | } 234 | 235 | // xcPlat: Cross compile for a particular platform 236 | // 0.3.0 - breaking change - changed 'call []string' to 'workingDirectory string'. 237 | func xcPlat(dest platforms.Platform, tp TaskParams, exeName string, packagePath string) (string, error) { 238 | if tp.Settings.IsVerbose() { 239 | log.Printf("building %s for platform %v.", exeName, dest) 240 | } 241 | args := []string{} 242 | absoluteBin, err := core.GetAbsoluteBin(dest.Os, dest.Arch, tp.Settings.AppName, exeName, tp.WorkingDirectory, tp.Settings.GetFullVersionName(), tp.Settings.OutPath, tp.Settings.ArtifactsDest) 243 | if err != nil { 244 | return "", err 245 | } 246 | outDir := filepath.Dir(absoluteBin) 247 | err = os.MkdirAll(outDir, 0755) 248 | if err != nil { 249 | return "", err 250 | } 251 | args = append(args, "-o", absoluteBin, ".") 252 | //log.Printf("building %s", exeName) 253 | //v0.8.5 no longer using CGO_ENABLED 254 | envExtra := []string{"GOOS=" + dest.Os, "GOARCH=" + dest.Arch} 255 | if dest.Os == platforms.LINUX && dest.Arch == platforms.ARM { 256 | // see http://dave.cheney.net/2012/09/08/an-introduction-to-cross-compilation-with-go 257 | goarm := tp.Settings.GetTaskSettingString(TASK_XC, "GOARM") 258 | if goarm != "" { 259 | envExtra = append(envExtra, "GOARM="+goarm) 260 | } 261 | } 262 | err = executils.InvokeGo(packagePath, "build", args, envExtra, tp.Settings) 263 | return absoluteBin, err 264 | } 265 | -------------------------------------------------------------------------------- /testdata/test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func Test() { 6 | fmt.Println("This should not build") 7 | } 8 | 9 | func main() { 10 | 11 | fmt.Println("This should not get built") 12 | } 13 | -------------------------------------------------------------------------------- /typeutils/mapstringinterfaceutils.go: -------------------------------------------------------------------------------- 1 | // helps with type coercion and merging maps 2 | package typeutils 3 | 4 | /* 5 | Copyright 2013 Am Laher 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | import ( 21 | "fmt" 22 | ) 23 | 24 | // coerce interface{} to slice of strings. 25 | func ToStringSlice(v interface{}, k string) ([]string, error) { 26 | ret := []string{} 27 | switch typedV := v.(type) { 28 | case []interface{}: 29 | for _, i := range typedV { 30 | ret = append(ret, i.(string)) 31 | } 32 | return ret, nil 33 | } 34 | return ret, fmt.Errorf("%s should be a json array, not a %T", k, v) 35 | } 36 | 37 | // coerce interface{} to string. 38 | func ToString(v interface{}, k string) (string, error) { 39 | switch typedV := v.(type) { 40 | case string: 41 | return typedV, nil 42 | } 43 | return "", fmt.Errorf("%s should be a json string, not a %T", k, v) 44 | } 45 | 46 | // coerce interface{} to bool 47 | func ToBool(v interface{}, k string) (bool, error) { 48 | switch typedV := v.(type) { 49 | case bool: 50 | return typedV, nil 51 | case string: 52 | switch typedV { 53 | case "true": 54 | return true, nil 55 | case "True": 56 | return true, nil 57 | case "TRUE": 58 | return true, nil 59 | case "1": 60 | return true, nil 61 | default: 62 | return false, nil 63 | } 64 | } 65 | return false, fmt.Errorf("%s should be a json boolean, not a %T", k, v) 66 | } 67 | 68 | // coerce interface{} to float64 69 | func ToFloat64(v interface{}, k string) (float64, error) { 70 | switch typedV := v.(type) { 71 | case float64: 72 | return typedV, nil 73 | } 74 | return 0, fmt.Errorf("%s should be a json int, not a %T", k, v) 75 | } 76 | 77 | // coerce interface{} to int 78 | func ToInt(v interface{}, k string) (int, error) { 79 | switch typedV := v.(type) { 80 | case int: 81 | return typedV, nil 82 | } 83 | return 0, fmt.Errorf("%s should be a json int, not a %T", k, v) 84 | } 85 | 86 | // coerce interface{} to map[string]interface{} 87 | func ToMap(v interface{}, k string) (map[string]interface{}, error) { 88 | switch typedV := v.(type) { 89 | case map[string]interface{}: 90 | return typedV, nil 91 | } 92 | return nil, fmt.Errorf("%s should be a json map, not a %T", k, v) 93 | } 94 | 95 | // coerce interface{} to map[string]map[string]interface{} 96 | func ToMapStringMapStringInterface(v interface{}, k string) (map[string]map[string]interface{}, error) { 97 | switch typedV := v.(type) { 98 | case map[string]interface{}: 99 | ret := make(map[string]map[string]interface{}) 100 | for k, v := range typedV { 101 | typedSubV, err := ToMap(v, k) 102 | ret[k] = typedSubV 103 | if err != nil { 104 | return nil, fmt.Errorf("%s should be a json map[string]map[string]interface{}, not a %T", k, v) 105 | } 106 | } 107 | return ret, nil 108 | } 109 | return nil, fmt.Errorf("%s should be a json map[string]map[string]interface{}, not a %T", k, v) 110 | } 111 | 112 | // merge nested maps (first argument takes priority) 113 | // note that lists are replaced, not merged 114 | func MergeMapsStringMapStringInterface(high, low map[string]map[string]interface{}) map[string]map[string]interface{} { 115 | if high == nil { 116 | return low 117 | } 118 | for key, lowValTyped := range low { 119 | if highValTyped, keyExists := high[key]; keyExists { 120 | // NOTE: go deeper for maps. 121 | // (Slices and other types should not go deeper) 122 | high[key] = MergeMaps(highValTyped, lowValTyped) 123 | } else { 124 | high[key] = lowValTyped 125 | } 126 | } 127 | return high 128 | } 129 | 130 | func AreMapStringMapStringInterfacesEqual(a, b map[string]map[string]interface{}) bool { 131 | if a == nil { 132 | return b == nil 133 | } else { 134 | if len(a) != len(b) { 135 | return false 136 | } 137 | for key, aVal := range a { 138 | bVal := b[key] 139 | if !AreMapsEqual(aVal, bVal) { 140 | return false 141 | } 142 | } 143 | } 144 | return true 145 | } 146 | 147 | func AreMapsEqual(a, b map[string]interface{}) bool { 148 | if a == nil { 149 | return b == nil 150 | } else { 151 | for key, aVal := range a { 152 | if bVal, bKeyExists := b[key]; bKeyExists { 153 | if aVal != bVal { 154 | return false 155 | } 156 | } else { 157 | return false 158 | } 159 | } 160 | for key, bVal := range b { 161 | if aVal, aKeyExists := a[key]; aKeyExists { 162 | if aVal != bVal { 163 | return false 164 | } 165 | } else { 166 | return false 167 | } 168 | } 169 | } 170 | return true 171 | } 172 | 173 | // merge possibly-nested maps (first argument takes priority) 174 | // note that lists are replaced, not merged 175 | func MergeMaps(high, low map[string]interface{}) map[string]interface{} { 176 | if high == nil { 177 | return low 178 | } 179 | for key, lowVal := range low { 180 | if highVal, keyExists := high[key]; keyExists { 181 | // NOTE: go deeper for maps. 182 | // (Slices and other types should not go deeper) 183 | switch highValTyped := highVal.(type) { 184 | case map[string]interface{}: 185 | switch lowValTyped := lowVal.(type) { 186 | case map[string]interface{}: 187 | high[key] = MergeMaps(highValTyped, lowValTyped) 188 | } 189 | } 190 | } else { 191 | high[key] = lowVal 192 | } 193 | } 194 | return high 195 | } 196 | -------------------------------------------------------------------------------- /typeutils/stringslices.go: -------------------------------------------------------------------------------- 1 | package typeutils 2 | 3 | /* 4 | Copyright 2013 Am Laher 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // deletes a given index 20 | func StringSliceDelIndex(a []string, i int) []string { 21 | a = append(a[:i], a[i+1:]...) 22 | // OR a = a[:i+copy(a[i:], a[i+1:])] 23 | return a 24 | } 25 | 26 | // deletes first occurence of a given string 27 | func StringSliceDel(slice []string, value string) []string { 28 | p := StringSlicePos(slice, value) 29 | if p > -1 { 30 | return StringSliceDelIndex(slice, p) 31 | } 32 | return slice 33 | } 34 | 35 | // deletes all occurences of a given string 36 | func StringSliceDelAll(slice []string, value string) []string { 37 | p := StringSlicePos(slice, value) 38 | for p > -1 { 39 | slice = StringSliceDelIndex(slice, p) 40 | p = StringSlicePos(slice, value) 41 | } 42 | return slice 43 | } 44 | 45 | // returns first index of a given string 46 | func StringSlicePos(slice []string, value string) int { 47 | for p, v := range slice { 48 | if v == value { 49 | return p 50 | } 51 | } 52 | return -1 53 | } 54 | 55 | // returns true if a slice contains given string 56 | func StringSliceContains(slice []string, value string) bool { 57 | return StringSlicePos(slice, value) > -1 58 | } 59 | 60 | func StringSliceEquals(a, b []string) bool { 61 | if len(a) != len(b) { 62 | return false 63 | } 64 | for i, c := range a { 65 | if c != b[i] { 66 | return false 67 | } 68 | } 69 | return true 70 | } 71 | 72 | //this was taken from golang `bytes.Compare`. 73 | func StringSliceCompare(a, b []string) int { 74 | m := len(a) 75 | if m > len(b) { 76 | m = len(b) 77 | } 78 | for i, ac := range a[0:m] { 79 | bc := b[i] 80 | switch { 81 | case ac > bc: 82 | return 1 83 | case ac < bc: 84 | return -1 85 | } 86 | } 87 | switch { 88 | case len(a) < len(b): 89 | return -1 90 | case len(a) > len(b): 91 | return 1 92 | } 93 | return 0 94 | } 95 | -------------------------------------------------------------------------------- /typeutils/stringslices_test.go: -------------------------------------------------------------------------------- 1 | package typeutils 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestStringSliceEquals(t *testing.T) { 8 | expected := []string{"-l", "-X"} 9 | actual := []string{"-l", "-X"} 10 | if !StringSliceEquals(actual, expected) { 11 | t.Fatalf("unexpected result %v != %v", actual, expected) 12 | } 13 | actual = append(actual, "3") 14 | if StringSliceEquals(actual, expected) { 15 | t.Fatalf("unexpected result %v == %v", actual, expected) 16 | } 17 | 18 | actual2 := []string{"sdfssdfsdfsdf", "-dsdsdsfds werwer"} 19 | if StringSliceEquals(actual2, expected) { 20 | t.Fatalf("unexpected result %v == %v", actual2, expected) 21 | } 22 | } 23 | --------------------------------------------------------------------------------