├── 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 |
--------------------------------------------------------------------------------