├── LICENSE ├── README.md ├── TODO ├── builder.go ├── collector.go ├── flagcompiler.go ├── gcc.go ├── main.go ├── msvc.go ├── scriptgen.go ├── step.go ├── target.go └── toolchain.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Pietro Gagliardi 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | (this is called the MIT License or Expat License; see http://www.opensource.org/licenses/MIT) 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qo: a build system for C/C++ 2 | 3 | qo is a new build system for C and C++ (though I can add other languages later). In contrast to existing build systems, which require the use of not only a Makefile but also an assortment of complex configuration files (or multiple stages thereof), qo doesn't use any. Instead, custom build settings are embedded using simple directives directly into the source code of your program. qo conditionally compiles each source file based on its filename. qo also supports some resource files normally compiled into the program. Debug builds and cross-compiles are also intended to be done as easily as possible. 4 | 5 | Enjoy! Suggestions, fixes, etc. welcome. 6 | 7 | ## News 8 | **14 April 2015**
Added a `-nounix` flag to inhibit the `unix` pseudo-OS. 9 | 10 | **6 April 2015**
Added `unix` pseudo-OS. 11 | 12 | **2 March 2015**
I rewrote the actual build script part of the program a bit: some of the internal names have changed, a script can have an arbitrary number of stages (groups of steps that must be completed before the next group can start), and most important, **load balancing**. A future change will provide an option to change the number of concurrent build steps (currently set to [the number of CPU cores on your system](http://golang.org/pkg/runtime/#NumCPU)). Please report any bugs should this have broken anything. 13 | 14 | ## Installing 15 | qo is written in [Go](http://golang.org/). It has no outside dependencies and does not use cgo, so a compiled qo binary is statically linked and ready to run out of the box. Once the project matures more, I may offer prebuilt binaries for download. 16 | 17 | ## Getting Started 18 | Let's say you have a simple project in a directory: 19 | 20 | ``` 21 | $ cd project 22 | $ ls 23 | file1.c file2.c file3.c file4.c project.h 24 | ``` 25 | 26 | To build this project as it stands, simply invoke qo with no arguments: 27 | 28 | ``` 29 | $ qo 30 | [ 0%] Beginning build 31 | [ 20%] Compiled file1.c 32 | [ 40%] Compiled file3.c 33 | [ 60%] Compiled file4.c 34 | [ 80%] Compiled file2.c 35 | [100%] Linked project 36 | ``` 37 | 38 | You should see the status of the build as it happens (as above), and upon completion, the compiled program will be left as the executable `project` (named after the project directory) in the project directory, ready for running: 39 | 40 | ``` 41 | $ ./project 42 | ``` 43 | 44 | To build a debug version, pass `-g`: 45 | 46 | ``` 47 | $ qo -g 48 | ``` 49 | 50 | To see the individual commands as they happen, pass `-x`. 51 | 52 | Note that qo automatically builds with as many reasonable compiler diagnostics as possible enabled. 53 | 54 | ## What is Built? 55 | qo scans the current directory and all subdirectories for files to build. Files matched have the given (case-insensitive) extensions: 56 | 57 | * C files: `.c` 58 | * C++ files: `.cpp`, `.cxx`, `.c++`, `.cc` 59 | * note the case-insensitive part; `.C` is recognized as C, not C++ 60 | * C header files: `.h`, `.hpp`, `.hxx`, `.h++`, `.hh` 61 | * Objective-C files: `.m` 62 | * Objective-C++ files: `.mm` 63 | * Windows Resource files: `.rc` 64 | 65 | Files can be excluded from the build if they are meant for a different operating system and/or CPU architecture; this is also done by filename and is described below, under "Cross-Compiling". 66 | 67 | C files are assumed to be C99. C++ files are assumed to be C++11. 68 | 69 | ## Configuring the Build 70 | So how do you specify extra libraries or compiler options for a project? Simple: you include special directives in the source files! Directives take the form 71 | 72 | ``` 73 | // #qo directive: arguments 74 | ``` 75 | 76 | where whitespace up to and including the first space after `#qo` is significant, and where the `//` must be the first thing on the line. 77 | 78 | The two most important (and most portable) directives are `pkg-config` and `LIBS`. `pkg-config` passes the package names listed in `arguments` to `pkg-config`, inserting the resultant compiler flags as needed. `LIBS` takes the library names in `arguments` and passes them to the linker, applying the correct argument format for the toolchain in use (see "Cross-Compiling" below). For example: 79 | 80 | ``` 81 | // #qo pkg-config: gtk+-3.0 82 | // #qo LIBS: pwquality sqlite3 83 | ``` 84 | 85 | For more control over the command lines for compiling each file, the `CFLAGS`, `CXXFLAGS`, and `LDFLAGS` directives pass their `arguments` as extra arguments to the C compiler, C++ compiler, and linker, respectively. 86 | 87 | `#qo` directives are assembled from all source files together. That is, do not copy the directives into each source file; bad things will happen. 88 | 89 | In addition, the `$CFLAGS`, `$CXXFLAGS`, and `$LDFLAGS` environment variables also change compiler/linker command-line arguments. 90 | 91 | ## Cross-Compiling 92 | qo tries to make cross-compiling easy. There are three concepts at play: 93 | 94 | - the target OS 95 | - the target architecture 96 | - the toolchain, which defines which compilers and linkers to use 97 | 98 | By default, qo builds for the system you are presently running (actually the system the qo binary was built for; but this is a limitation of Go). This is called the host. You can change the target OS, target arch, or toolchain with the `-os`, `-arch`, and `-tc` options, respectively. Pass `list` to see a list of supported OSs, architectures, and toolchains. 99 | 100 | (qo by default tends toward gcc/clang-based toolchains.) 101 | 102 | In addition, qo will omit files and folders from the build if they are intended for a different OS and/or architecture than the target. To omit a file, have `_OS`, `_arch`, or `_OS_arch` before the extension. To omit a folder, its name must consist entirely of `OS`, `arch`, or `OS_arch`. For example: 103 | 104 | ``` 105 | file.c compiled always 106 | file_windows.c only compiled if targetting Windows 107 | file_386.c only compiled if targetting architecture 386 108 | file_windows_386.c only compiled if targetting 386-based Windows 109 | directory/ trasversed always 110 | windows/ only trasversed on Windows 111 | 386/ only trasversed if targetting 386 112 | windows_386/ only trasversed if targetting 386-based Windows 113 | ``` 114 | 115 | In addition, the OS name `unix` is valid on all Unix systems (Linux, FreeBSD, Mac OS X, etc.). You can choose to inhibit this with the `-nounix` flag. 116 | 117 | ## Cross-Compiler Executable Search Order 118 | Under the hood, however, cross-compiling is a very complex and problematic undertaking for historical and practical reasons. qo assumes you have a correctly configured cross-compiler setup for the target OS, architecture, and toolchain (even if it's just the toolchain). 119 | 120 | qo makes the following compromise. Given the following terms: 121 | 122 | **unqualified binaries** - binaries named `gcc`, `g++`, `clang`, and `clang++`, without any target triplet
123 | **multilib flags** - `-m32` and `-m64` 124 | 125 | 1. If the `-triplet` option is passed to qo to explicitly specify a triplet to use, that triplet is used, no questions asked. No mulitlib flags will be appended to the command line. 126 | 2. Otherwise, if the target is the same as the host, unqualified binaries are run, and multilib flags may or may not be appended. 127 | 3. Otherwise, if the target OS is the same as the host OS and host OS is not Windows, if the host arch is either `386` or `amd64` and the target arch is either `386` or `amd64`, a multilib flag is appended, and the unqualified binaries are run. 128 | 4. Otherwise, if using clang, a generic target triplet is generated and used. 129 | 5. Otherwise, if the target OS is windows, MinGW-w64 binaries are used. 130 | 6. Otherwise, an error occurs. 131 | 132 | For more information, see [this](http://stackoverflow.com/a/26101710/3408572) and its references. 133 | 134 | ## Notes 135 | ### A note on optional features and cyclic dependencies 136 | qo does not support the notion of optional features: everything in the recursive directory tree of the current directory is compiled. I personally don't like features being optional; if something really needs to be conditional, it should be a plugin, and there's no reason to ship a gimped or feature-incomplete version of a program. I don't like how graphviz packages in at least Ubuntu don't ship with pic support (even though I'm probably the only person int he world that still uses troff). 137 | 138 | In a related vein, cyclic dependencies (usually caused by optional features, as is the case with GLib ↔ GVFS) should also be avoided. 139 | 140 | ### Notes on MSVC 141 | The version of MSVC used defines how much C99 or C++11 can be used. 142 | 143 | The following seem to be undocumented as being MinGW extensions to the rc format: 144 | - arithmetic expressions 145 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - specify target type (exe, so/dll) 2 | - figure out what to do about assembly language 3 | - figure out how to change debug symbol format on Mac OS X and MSVC to one embedded in the binary (rather than .dSYM and .pdb files, respectively) 4 | - toolchain-specific pkg-configs and pkg-config options for msvc 5 | - somehow figure out how to add -I (and others?) to rc 6 | - sdl-config? wx-config? 7 | - in-source build tag exclusion 8 | - a 'unix' OS tag (requires in-source build tag exclusions) 9 | - set up a FreeBSD target that uses cc? or make it just require clang? 10 | - build files in their directories, not the current one 11 | - build named source files here 12 | - LOAD BALANCING 13 | - link against the real visual c++ runtime, not the windows private one 14 | [12:56] andlabs well, partial you are right here 15 | [12:57] you should use here always -static option, too. As otherwise other gcc-libraries (as libgcc.DLL, libstdc++.dll, etc) getting linked in, as pe-coff is finally linked, they are linked on gcc's bootstrap against msvcrt.dll 16 | - also note https://support.microsoft.com/en-us/kb/2661358 17 | - pkg-config cross-compiling notes: http://www.freedesktop.org/wiki/Software/pkg-config/CrossCompileProposal/ 18 | - add -Wno-switch to silence missing enum cases warnings 19 | 20 | * TODO also need to add: 21 | * gresource files: `.xml` with root tag `` 22 | * Qt moc files (will need some way to distinguish; same as C headers) 23 | * Qt Designer files: `.ui` as XML with root tag `` 24 | * anything else (send ideas!) 25 | * TODO can these be embedded? 26 | * gettext files: `.po` 27 | * Qt Linguist files: `.ts` as XML with root t ag `` 28 | * anything else (send ideas!) 29 | -------------------------------------------------------------------------------- /builder.go: -------------------------------------------------------------------------------- 1 | // 24 september 2014 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "runtime" 9 | "sync" 10 | ) 11 | 12 | var percentPer float64 13 | var progress float64 14 | 15 | func printProgress(step string) { 16 | fmt.Printf("[%3d%%] %s\n", int(progress), step) 17 | } 18 | 19 | func runner(in <-chan *Step, out chan<- *Step, wg *sync.WaitGroup) { 20 | for s := range in { 21 | s.Do() 22 | out <- s 23 | } 24 | wg.Done() 25 | } 26 | 27 | func runStage(s Stage) { 28 | wg := new(sync.WaitGroup) 29 | in := make(chan *Step) 30 | out := make(chan *Step) 31 | // TODO make an option 32 | for i := 0; i < runtime.NumCPU(); i++ { 33 | wg.Add(1) 34 | go runner(in, out, wg) 35 | } 36 | wg.Add(1) 37 | go func() { 38 | for i := 0; i < len(s); i++ { 39 | in <- s[i] 40 | } 41 | close(in) 42 | wg.Done() 43 | }() 44 | for i := 0; i < len(s); i++ { 45 | s := <-out 46 | fmt.Fprintf(os.Stderr, "%s", s.Output.Bytes()) 47 | // ensure only one newline 48 | if s.Output.Len() != 0 && s.Output.Bytes()[s.Output.Len() - 1] != '\n' { 49 | fmt.Fprintf(os.Stderr, "\n") 50 | } 51 | if s.Error != nil { 52 | fail("Step %q failed with error: %v", s.Name, s.Error) 53 | } 54 | progress += percentPer 55 | printProgress(s.Name) 56 | } 57 | wg.Wait() 58 | close(out) 59 | } 60 | 61 | func run() { 62 | percentPer = 100 / float64(nSteps) 63 | progress = 0 64 | printProgress("Beginning build") 65 | for _, stage := range script { 66 | runStage(stage) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /collector.go: -------------------------------------------------------------------------------- 1 | // 24 september 2014 2 | 3 | package main 4 | 5 | import ( 6 | "os" 7 | "strings" 8 | "path/filepath" 9 | ) 10 | 11 | var cfiles []string 12 | var cppfiles []string 13 | var hfiles []string 14 | var mfiles []string 15 | var mmfiles []string 16 | var rcfiles []string 17 | 18 | var base string 19 | 20 | func consider(list *[]string, path string) { 21 | if excludeFile(path) { 22 | return 23 | } 24 | *list = append(*list, path) 25 | } 26 | 27 | func walker(path string, info os.FileInfo, err error) error { 28 | if err != nil { 29 | return err 30 | } 31 | if base := filepath.Base(path); base != "." && base != ".." && base[0] == '.' { 32 | if info.IsDir() { 33 | return filepath.SkipDir 34 | } 35 | return nil 36 | } 37 | if info.IsDir() { 38 | if excludeDir(path) { 39 | return filepath.SkipDir 40 | } 41 | return nil 42 | } 43 | switch strings.ToLower(filepath.Ext(path)) { 44 | case ".c": 45 | consider(&cfiles, path) 46 | case ".cpp", ".cxx", ".c++", ".cc": 47 | consider(&cppfiles, path) 48 | case ".h", ".hpp", ".hxx", ".h++", ".hh": 49 | consider(&hfiles, path) 50 | case ".m": 51 | consider(&mfiles, path) 52 | case ".mm": 53 | consider(&mmfiles, path) 54 | case ".rc": 55 | consider(&rcfiles, path) 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /flagcompiler.go: -------------------------------------------------------------------------------- 1 | // 24 september 2014 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "os" 8 | "strings" 9 | "bufio" 10 | "os/exec" 11 | ) 12 | 13 | var debug = flag.Bool("g", false, "build with debug symbols") 14 | 15 | var toolchain Toolchain 16 | 17 | var cflags []string 18 | var cxxflags []string 19 | var ldflags []string 20 | var libs []string 21 | 22 | func pkgconfig(which string, pkgs []string) []string { 23 | cmd := exec.Command("pkg-config", append([]string{which}, pkgs...)...) 24 | cmd.Stderr = os.Stderr 25 | output, err := cmd.Output() 26 | if err != nil { 27 | fail("Error runing pkg-config: %v", err) 28 | } 29 | return strings.Fields(string(output)) 30 | } 31 | 32 | func parseFile(filename string) { 33 | f, err := os.Open(filename) 34 | if err != nil { 35 | fail("Error opening %s to scan for #qo directives: %v", filename, err) 36 | } 37 | defer f.Close() 38 | r := bufio.NewScanner(f) 39 | 40 | for r.Scan() { 41 | line := r.Text() 42 | if !strings.HasPrefix(line, "// #qo ") { 43 | continue 44 | } 45 | line = line[len("// #qo "):] 46 | parts := strings.Fields(line) 47 | switch parts[0] { 48 | case "CFLAGS:": 49 | cflags = append(cflags, parts[1:]...) 50 | case "CXXFLAGS:": 51 | cxxflags = append(cxxflags, parts[1:]...) 52 | case "LDFLAGS:": 53 | ldflags = append(ldflags, parts[1:]...) 54 | case "pkg-config:": 55 | xcflags := pkgconfig("--cflags", parts[1:]) 56 | xlibs := pkgconfig("--libs", parts[1:]) 57 | cflags = append(cflags, xcflags...) 58 | cxxflags = append(cxxflags, xcflags...) 59 | ldflags = append(ldflags, xlibs...) 60 | case "LIBS:": 61 | for i := 1; i < len(parts); i++ { 62 | libs = append(libs, parts[i]) 63 | } 64 | default: 65 | fail("Invalid #qo directive %q in %s", parts[0], filename) 66 | } 67 | } 68 | if err := r.Err(); err != nil { 69 | fail("Error reading %s to scan for #qo directives: %v", filename, err) 70 | } 71 | } 72 | 73 | func compileFlags() { 74 | if *selectedToolchain == "" { 75 | *selectedToolchain = "gcc" 76 | if *targetOS == "darwin" { 77 | *selectedToolchain = "clang" 78 | } 79 | } 80 | toolchain = toolchains[*selectedToolchain] 81 | toolchain.Prepare() 82 | 83 | cflags = append(cflags, strings.Fields(os.Getenv("CFLAGS"))...) 84 | cxxflags = append(cxxflags, strings.Fields(os.Getenv("CXXFLAGS"))...) 85 | ldflags = append(ldflags, strings.Fields(os.Getenv("LDFLAGS"))...) 86 | 87 | for _, f := range cfiles { 88 | parseFile(f) 89 | } 90 | for _, f := range cppfiles { 91 | parseFile(f) 92 | } 93 | for _, f := range hfiles { 94 | parseFile(f) 95 | } 96 | for _, f := range mfiles { 97 | parseFile(f) 98 | } 99 | for _, f := range mmfiles { 100 | parseFile(f) 101 | } 102 | for _, f := range rcfiles { 103 | parseFile(f) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /gcc.go: -------------------------------------------------------------------------------- 1 | // 27 september 2014 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "runtime" 8 | ) 9 | 10 | type GCCBase struct { 11 | CC string 12 | CXX string 13 | LD string 14 | RC string 15 | ArchFlag []string 16 | } 17 | 18 | func (g *GCCBase) buildRegularFile(cc string, std string, cflags []string, filename string) (stages []Stage, object string) { 19 | object = objectName(filename, ".o") 20 | line := []string{ 21 | cc, 22 | filename, 23 | "-c", 24 | std, 25 | "-Wall", 26 | "-Wextra", 27 | // for the case where we are implementing an interface and are not using some parameter 28 | "-Wno-unused-parameter", 29 | } 30 | if g.ArchFlag != nil { 31 | line = append(line, g.ArchFlag...) 32 | } 33 | line = append(line, cflags...) 34 | if *debug { 35 | line = append(line, "-g") 36 | } 37 | line = append(line, "-o", object) 38 | s := &Step{ 39 | Name: "Compiled " + filename, 40 | Line: line, 41 | } 42 | stages = []Stage{ 43 | Stage{s}, 44 | } 45 | return stages, object 46 | } 47 | 48 | func (g *GCCBase) BuildCFile(filename string, cflags []string) (stages []Stage, object string) { 49 | return g.buildRegularFile( 50 | g.CC, 51 | "--std=c99", // I refuse to support C11. 52 | cflags, 53 | filename) 54 | } 55 | 56 | func (g *GCCBase) BuildCXXFile(filename string, cflags []string) (stages []Stage, object string) { 57 | g.LD = g.CXX 58 | return g.buildRegularFile( 59 | g.CXX, 60 | "--std=c++11", 61 | cflags, 62 | filename) 63 | } 64 | 65 | // apart from needing -lobjc at link time, Objective-C/C++ are identical to C/C++; the --std flags are the same (thanks Beelsebob in irc.freenode.net/#macdev) 66 | // TODO provide -lobjc? 67 | 68 | func (g *GCCBase) BuildMFile(filename string, cflags []string) (stages []Stage, object string) { 69 | return g.BuildCFile(filename, cflags) 70 | } 71 | 72 | func (g *GCCBase) BuildMMFile(filename string, cflags []string) (stages []Stage, object string) { 73 | return g.BuildCXXFile(filename, cflags) 74 | } 75 | 76 | func (g *GCCBase) BuildRCFile(filename string, cflags []string) (stages []Stage, object string) { 77 | if g.RC == "" { 78 | fail("LLVM/clang does not come with a Windows resource compiler (if this message appears in other situations in error, contact andlabs)") 79 | } 80 | object = objectName(filename, ".o") 81 | line := append([]string{ 82 | g.RC, 83 | filename, 84 | object, 85 | }, cflags...) 86 | s := &Step{ 87 | Name: "Compiled " + filename, 88 | Line: line, 89 | } 90 | stages = []Stage{ 91 | Stage{s}, 92 | } 93 | return stages, object 94 | } 95 | 96 | func (g *GCCBase) Link(objects []string, ldflags []string, libs []string) *Step { 97 | if g.LD == "" { 98 | g.LD = g.CC 99 | } 100 | target := targetName() 101 | for i := 0; i < len(libs); i++ { 102 | libs[i] = "-l" + libs[i] 103 | } 104 | line := []string{ 105 | g.LD, 106 | } 107 | if g.ArchFlag != nil { 108 | line = append(line, g.ArchFlag...) 109 | } 110 | line = append(line, objects...) 111 | line = append(line, ldflags...) 112 | line = append(line, libs...) 113 | if *debug { 114 | line = append(line, "-g") 115 | } 116 | line = append(line, "-o", target) 117 | return &Step{ 118 | Name: "Linked " + target, 119 | Line: line, 120 | } 121 | } 122 | 123 | // TODO: 124 | // - MinGW static libgcc/libsjlj/libwinpthread/etc. 125 | // - -static-libgcc 126 | 127 | var triplet = flag.String("triplet", "", "gcc/clang target triplet to use; see README") 128 | 129 | var garchs = map[string]string{ 130 | "386": "i686", 131 | "amd64": "x86_64", 132 | } 133 | 134 | // 386 and amd64 are commonly configured using multilib rather than targets 135 | // everything else will be a nil slice 136 | var garchflags = map[string][]string{ 137 | "386": []string{"-m32"}, 138 | "amd64": []string{"-m64"}, 139 | } 140 | 141 | type GCC struct { 142 | *GCCBase 143 | } 144 | 145 | func isMultilib() bool { 146 | if *targetOS == runtime.GOOS && runtime.GOOS != "windows" { 147 | // MinGW for Windows is not multilib 148 | // assume everything else is 149 | if runtime.GOARCH == "386" || runtime.GOARCH == "amd64" { 150 | if *targetArch == "386" || *targetArch == "amd64" { 151 | // multilib only 152 | return true 153 | } 154 | } 155 | } 156 | return false 157 | } 158 | 159 | func (g *GCC) Prepare() { 160 | garch := garchs[*targetArch] 161 | prefix := "" 162 | if *triplet != "" { 163 | prefix = *triplet + "-" 164 | goto out 165 | } 166 | // set this before any of the following in case target == host 167 | g.ArchFlag = garchflags[*targetArch] 168 | if *targetOS == runtime.GOOS && *targetArch == runtime.GOARCH { 169 | return 170 | } 171 | if isMultilib() { 172 | return 173 | } 174 | switch *targetOS { 175 | case "windows": 176 | prefix = garch + "-w64-mingw32-" 177 | case "linux": 178 | // TODO abi override 179 | prefix = garch + "-linux-gnu-" 180 | default: 181 | fail("Sorry, cross-compiling for gcc on %s requires specifying an explicit target triple with -target", *targetOS) 182 | } 183 | out: 184 | g.CC = prefix + g.CC 185 | g.CXX = prefix + g.CXX 186 | g.RC = prefix + g.RC 187 | } 188 | 189 | type Clang struct { 190 | *GCCBase 191 | } 192 | 193 | // see the LLVM source; file include/llvm/ADT/Triple.h (thanks jroelofs in irc.oftc.net/#llvm) 194 | var clangOS = map[string]string{ 195 | "windows": "win32", 196 | "linux": "linux", 197 | "darwin": "darwin", 198 | "freebsd": "freebsd", 199 | "openbsd": "openbsd", 200 | "netbsd": "netbsd", 201 | "dragonfly": "dragonfly", 202 | "solaris": "solaris", 203 | } 204 | 205 | func (g *Clang) Prepare() { 206 | if *triplet != "" { 207 | g.ArchFlag = []string{"-target", *triplet} 208 | return 209 | } 210 | // set this before any of the following in case target == host 211 | g.ArchFlag = garchflags[*targetArch] 212 | if *targetOS == runtime.GOOS && *targetArch == runtime.GOARCH { 213 | return 214 | } 215 | if isMultilib() { 216 | return 217 | } 218 | // clang makes the job easier 219 | // TODO abi override 220 | g.ArchFlag = append(g.ArchFlag, "-target", garchs[*targetArch] + "-" + clangOS[*targetOS]) 221 | } 222 | 223 | func init() { 224 | toolchains["gcc"] = &GCC{ 225 | GCCBase: &GCCBase{ 226 | CC: "gcc", 227 | CXX: "g++", 228 | RC: "windres", 229 | }, 230 | } 231 | toolchains["clang"] = &Clang{ 232 | GCCBase: &GCCBase{ 233 | CC: "clang", 234 | CXX: "clang++", 235 | // no RC in clang 236 | }, 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // 24 september 2014 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "flag" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | func fail(format string, args ...interface{}) { 14 | fmt.Fprintf(os.Stderr, "[FAIL] ") 15 | fmt.Fprintf(os.Stderr, format, args...) 16 | fmt.Fprintf(os.Stderr, "\n") 17 | os.Exit(1) 18 | } 19 | 20 | func main() { 21 | flag.Parse() 22 | if *selectedToolchain == "list" { 23 | listToolchains() 24 | os.Exit(0) 25 | } 26 | if *targetOS == "list" { 27 | fmt.Printf("%s\n", strings.Join(supportedOSs, "\n")) 28 | os.Exit(0) 29 | } 30 | if *targetArch == "list" { 31 | fmt.Printf("%s\n", strings.Join(supportedArchs, "\n")) 32 | os.Exit(0) 33 | } 34 | computeExcludeSuffixes() 35 | err := filepath.Walk(".", walker) 36 | if err != nil { 37 | fail("Error collecting files: %v", err) 38 | } 39 | compileFlags() 40 | buildScript() 41 | err = os.MkdirAll(".qoobj", 0755) 42 | if err != nil { 43 | fail("Error making work directory .qoobj: %v", err) 44 | } 45 | run() 46 | os.Exit(0) // success 47 | } 48 | -------------------------------------------------------------------------------- /msvc.go: -------------------------------------------------------------------------------- 1 | // 27 september 2014 2 | 3 | package main 4 | 5 | import ( 6 | // ... 7 | ) 8 | 9 | type MSVC struct { } 10 | 11 | // TODO /MDd, /MTd 12 | // TODO /EHa? 13 | 14 | func (m *MSVC) buildRegularFile(std string, cflags []string, filename string) (stages []Stage, object string) { 15 | object = objectName(filename, ".o") 16 | line := append([]string{ 17 | "cl", 18 | filename, 19 | "/c", 20 | std, 21 | "/analyze", 22 | "/bigobj", 23 | "/nologo", 24 | "/RTC1", 25 | "/RTCc", 26 | "/RTCs", 27 | "/RTCu", 28 | "/sdl", 29 | "/Wall", 30 | "/Wp64", 31 | "/wd4100", // same as -Wno-unused-parameter 32 | // TODO equivalent of -Wno-switch 33 | }, cflags...) 34 | if *debug { 35 | line = append(line, "/Z7") // keep debug information in the object file 36 | } 37 | line = append(line, "/Fo" + object) // note: one parameter 38 | s := &Step{ 39 | Name: "Compiled " + filename, 40 | Line: line, 41 | } 42 | stages = []Stage{ 43 | Stage{s}, 44 | } 45 | return stages, object 46 | } 47 | 48 | func (m *MSVC) BuildCFile(filename string, cflags []string) (stages []Stage, object string) { 49 | return m.buildRegularFile( 50 | "/TC", 51 | cflags, 52 | filename) 53 | } 54 | 55 | func (m *MSVC) BuildCXXFile(filename string, cflags []string) (stages []Stage, object string) { 56 | return m.buildRegularFile( 57 | "/TP", 58 | cflags, 59 | filename) 60 | } 61 | 62 | func (m *MSVC) BuildMFile(filename string, cflags []string) (stages []Stage, object string) { 63 | fail("Objective-C unimplemented on MSVC") 64 | panic("unreachable") 65 | } 66 | 67 | func (m *MSVC) BuildMMFile(filename string, cflags []string) (stages []Stage, object string) { 68 | fail("Objective-C++ unimplemented on MSVC") 69 | panic("unreachable") 70 | } 71 | 72 | func (m *MSVC) BuildRCFile(filename string, cflags []string) (stages []Stage, object string) { 73 | resfile := objectName(filename, ".res") 74 | object = objectName(filename, ".o") 75 | rcline := append([]string{ 76 | "rc", 77 | // for rc, flags /must/ come first 78 | "/nologo", 79 | "/fo", resfile, // note: two parameters 80 | }, cflags...) 81 | rcline = append(rcline, filename) 82 | s := &Step{ 83 | Name: "Created RES file from " + filename, 84 | Line: rcline, 85 | } 86 | cvtline := append([]string{ 87 | "cvtres", 88 | // for cvtres, flags /must/ come first 89 | "/nologo", 90 | "/out:" + object, // note: one parameter 91 | resfile, 92 | }, cflags...) 93 | t := &Step{ 94 | Name: "Compiled object file from " + filename, 95 | Line: cvtline, 96 | } 97 | stages = []Stage{ 98 | Stage{s}, 99 | Stage{t}, 100 | } 101 | return stages, object 102 | } 103 | 104 | func (m *MSVC) Link(objects []string, ldflags []string, libs []string) *Step { 105 | target := targetName() 106 | for i := 0; i < len(libs); i++ { 107 | libs[i] = libs[i] + ".lib" 108 | } 109 | line := append([]string{ 110 | "link", 111 | "/largeaddressaware", 112 | "/nologo", 113 | }, objects...) 114 | line = append(line, ldflags...) 115 | line = append(line, libs...) 116 | if *debug { 117 | // TODO MSDN claims it's not possible to have embedded debug symbols (apparently COFF doesn't exist) 118 | } 119 | line = append(line, "/OUT:" + target) // note: one parameter 120 | return &Step{ 121 | Name: "Linked " + target, 122 | Line: line, 123 | } 124 | } 125 | 126 | func (m *MSVC) Prepare() { 127 | // nothing to do here 128 | } 129 | 130 | func init() { 131 | toolchains["msvc"] = &MSVC{} 132 | } 133 | -------------------------------------------------------------------------------- /scriptgen.go: -------------------------------------------------------------------------------- 1 | // 24 september 2014 2 | 3 | package main 4 | 5 | import ( 6 | "path/filepath" 7 | "strings" 8 | ) 9 | 10 | var script []Stage 11 | var nSteps int 12 | 13 | func objectName(filename string, suffix string) string { 14 | object := strings.Replace(filename, string(filepath.Separator), "_", -1) + suffix 15 | object = filepath.Join(".qoobj", object) 16 | return object 17 | } 18 | 19 | func mergeScript(s []Stage) { 20 | var i int 21 | 22 | // first append existing steps 23 | for i = 0; i < len(s); i++ { 24 | if i >= len(script) { 25 | break 26 | } 27 | script[i] = append(script[i], s[i]...) 28 | nSteps += len(s[i]) 29 | } 30 | // now add new stages 31 | for ; i < len(s); i++ { 32 | script = append(script, s[i]) 33 | nSteps += len(s[i]) 34 | } 35 | } 36 | 37 | func buildScript() { 38 | script = nil 39 | nSteps = 0 40 | 41 | objects := []string(nil) 42 | 43 | for _, f := range cfiles { 44 | s, obj := toolchain.BuildCFile(f, cflags) 45 | mergeScript(s) 46 | objects = append(objects, obj) 47 | } 48 | for _, f := range cppfiles { 49 | s, obj := toolchain.BuildCXXFile(f, cxxflags) 50 | mergeScript(s) 51 | objects = append(objects, obj) 52 | } 53 | for _, f := range mfiles { 54 | s, obj := toolchain.BuildMFile(f, cflags) 55 | mergeScript(s) 56 | objects = append(objects, obj) 57 | } 58 | for _, f := range mmfiles { 59 | s, obj := toolchain.BuildMMFile(f, cxxflags) 60 | mergeScript(s) 61 | objects = append(objects, obj) 62 | } 63 | for _, f := range rcfiles { 64 | s, obj := toolchain.BuildRCFile(f, nil) 65 | mergeScript(s) 66 | objects = append(objects, obj) 67 | } 68 | 69 | s := toolchain.Link(objects, ldflags, libs) 70 | script = append(script, Stage{s}) 71 | nSteps++ 72 | } 73 | -------------------------------------------------------------------------------- /step.go: -------------------------------------------------------------------------------- 1 | // 24 september 2014 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "flag" 10 | "bytes" 11 | "strings" 12 | ) 13 | 14 | var showall = flag.Bool("x", false, "show all commands as they run") 15 | 16 | // Step represents a single step in the build process. 17 | type Step struct { 18 | Name string 19 | Line []string 20 | Output *bytes.Buffer 21 | Error error 22 | } 23 | 24 | func (s *Step) Do() { 25 | if *showall { 26 | fmt.Printf("%s\n", strings.Join(s.Line, " ")) 27 | } 28 | cmd := exec.Command(s.Line[0], s.Line[1:]...) 29 | cmd.Env = os.Environ() 30 | s.Output = new(bytes.Buffer) 31 | cmd.Stdout = s.Output 32 | cmd.Stderr = s.Output 33 | s.Error = cmd.Run() 34 | } 35 | 36 | // Stage is a list of Steps. 37 | // Each Step in the Stage is run concurrently. 38 | // A build process consists of multiple Stages. 39 | // All Steps in the current Stage must be run to completion before other Steps will run. 40 | type Stage []*Step 41 | -------------------------------------------------------------------------------- /target.go: -------------------------------------------------------------------------------- 1 | // 24 september 2014 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "runtime" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | "sort" 12 | ) 13 | 14 | var targetOS = flag.String("os", runtime.GOOS, "select target OS; list for a list of supported OSs") 15 | var targetArch = flag.String("arch", runtime.GOARCH, "select target architecture; list for a list of supported architectures") 16 | 17 | func targetName() string { 18 | pwd, err := os.Getwd() 19 | if err != nil { 20 | fail("Error getting current working directory to determine target name: %v", err) 21 | } 22 | target := filepath.Base(pwd) 23 | if *targetOS == "windows" { 24 | target += ".exe" 25 | } 26 | return target 27 | } 28 | 29 | var supportedOSs = strings.Fields("windows darwin linux freebsd openbsd netbsd dragonfly solaris") 30 | var supportedArchs = strings.Fields("386 amd64") 31 | 32 | var isNotUnix = map[string]bool{ 33 | "windows": true, 34 | "plan9": true, 35 | } 36 | 37 | func init() { 38 | sort.Strings(supportedOSs) 39 | sort.Strings(supportedArchs) 40 | } 41 | 42 | var excludeSuffixes []string 43 | var excludeFolders []string 44 | 45 | var nounix = flag.Bool("nounix", false, "do not consider the current OS as a Unix system even if it would usually otherwise") 46 | 47 | func computeExcludeSuffixes() { 48 | for _, os := range supportedOSs { 49 | if os == *targetOS { 50 | continue 51 | } 52 | excludeSuffixes = append(excludeSuffixes, "_" + os) 53 | excludeFolders = append(excludeFolders, os) 54 | for _, arch := range supportedArchs { 55 | excludeSuffixes = append(excludeSuffixes, "_" + os + "_" + arch) 56 | excludeFolders = append(excludeFolders, os + "_" + arch) 57 | } 58 | } 59 | for _, arch := range supportedArchs { 60 | if arch == *targetArch { 61 | continue 62 | } 63 | excludeSuffixes = append(excludeSuffixes, "_" + arch) 64 | excludeFolders = append(excludeFolders, arch) 65 | excludeSuffixes = append(excludeSuffixes, "_" + *targetOS + "_" + arch) 66 | excludeFolders = append(excludeFolders, *targetOS + "_" + arch) 67 | } 68 | if *nounix || isNotUnix[*targetOS] { 69 | excludeSuffixes = append(excludeSuffixes, "_unix") 70 | excludeFolders = append(excludeFolders, "unix") 71 | for _, arch := range supportedArchs { 72 | excludeSuffixes = append(excludeSuffixes, "_unix_" + arch) 73 | excludeFolders = append(excludeFolders, "unix_" + arch) 74 | } 75 | } 76 | } 77 | 78 | func excludeFile(filename string) bool { 79 | base := filepath.Base(filename) 80 | ext := filepath.Ext(base) 81 | base = base[:len(base) - len(ext)] 82 | for _, suffix := range excludeSuffixes { 83 | if strings.HasSuffix(base, suffix) { 84 | return true 85 | } 86 | } 87 | return false 88 | } 89 | 90 | func excludeDir(filename string) bool { 91 | base := filepath.Base(filename) 92 | for _, exclude := range excludeFolders { 93 | if base == exclude { 94 | return true 95 | } 96 | } 97 | return false 98 | } 99 | -------------------------------------------------------------------------------- /toolchain.go: -------------------------------------------------------------------------------- 1 | // 24 september 2014 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "flag" 8 | "sort" 9 | ) 10 | 11 | type Toolchain interface { 12 | Prepare() 13 | BuildCFile(filename string, cflags []string) (stages []Stage, object string) 14 | BuildCXXFile(filename string, cflags []string) (stages []Stage, object string) 15 | BuildMFile(filename string, cflags []string) (stages []Stage, object string) 16 | BuildMMFile(filename string, cflags []string) (stages []Stage, object string) 17 | BuildRCFile(filename string, cflags []string) (stages []Stage, object string) 18 | Link(objects []string, ldflags []string, libs []string) *Step 19 | } 20 | 21 | var toolchains = make(map[string]Toolchain) 22 | 23 | // TODO: Plan 9 compilers (plan9) 24 | 25 | var selectedToolchain = flag.String("tc", "", "select toolchain; list for a full list of supported toolchains") 26 | 27 | func listToolchains() { 28 | tc := make([]string, 0, len(toolchains)) 29 | for k, _ := range toolchains { 30 | tc = append(tc, k) 31 | } 32 | sort.Strings(tc) 33 | for _, t := range tc { 34 | fmt.Printf("%s\n", t) 35 | } 36 | } 37 | --------------------------------------------------------------------------------