├── .github
└── FUNDING.yml
├── .gitignore
├── License.txt
├── Readme.md
├── Release.bat
├── compiler.go
├── compiler_darwin.go
├── compiler_linux.go
├── compiler_windows.go
├── conan.go
├── context.go
├── fileexists.go
├── getcompiler_darwin.go
├── getcompiler_linux.go
├── getcompiler_windows.go
├── getsourcefiles.go
├── go.mod
├── go.sum
├── main.go
└── package.go
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: openplanet
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | Release/
2 | .vscode/
3 |
--------------------------------------------------------------------------------
/License.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 - 2025 Melissa Geels
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # Quickbuild (qb)
2 | `qb` is a zero-configuration build system to very quickly build C/C++ projects on Linux, Windows, and MacOS.
3 |
4 |
5 |
6 |
7 |
8 | ## Example
9 | Let's say you have a folder containing some source files:
10 |
11 | ```c++
12 | // main.cpp
13 | #include "test.h"
14 | int main() {
15 | test();
16 | return 0;
17 | }
18 |
19 | // test.h
20 | void test();
21 |
22 | // test.cpp
23 | #include
24 | void test() {
25 | printf("Hello, world\n");
26 | }
27 | ```
28 |
29 | You run `qb` in this directory:
30 |
31 | ```
32 | ~/qbtest $ qb
33 | 22:30:40.363 | main.cpp
34 | 22:30:40.364 | test.cpp
35 | 22:30:40.456 | 👏 qbtest
36 | 22:30:40.456 | ⏳ compile 53.9738ms, link 39.1138ms
37 | ```
38 |
39 | And you run the resulting binary:
40 | ```
41 | ~/qbtest $ ./qbtest
42 | Hello, world
43 | ```
44 |
45 | ## Installing
46 | To install `qb`, make sure you have [Go](https://go.dev) installed, then run:
47 | ```
48 | go install github.com/codecat/qb@master
49 | ```
50 |
51 | ## Commands
52 | You can pass a number of commands to `qb`.
53 |
54 | ### `qb run`
55 | Runs the binary after building it.
56 |
57 | ### `qb clean`
58 | Cleans all output files that qb could generate.
59 |
60 | ## Optional configuration
61 | Since `qb` is meant to be a zero configuration tool, you don't have to do any configuration to get going quickly. It will do its best to find appropriate defaults for your setup, you just run `qb` and it builds.
62 |
63 | If you **do** want a little bit more control over what happens, you can either use command line flags or create a configuration file in your source folder.
64 |
65 | ### Command line options
66 | ```
67 | qb [--name name]
68 | [--out dir]
69 | [--type ]
70 | [--pkg name]
71 | [--static]
72 | [--debug]
73 | [--verbose]
74 | [--strict]
75 | [--exceptions ]
76 | [--optimize ]
77 | [--cppstd ]
78 | [--cstd ]
79 | [--include ]
80 | [--define ]
81 | ```
82 |
83 | #### `--name`
84 | Sets the name of the project and controls the output filename. You should not provide any file extension here as it will be added automatically.
85 |
86 | If no name is passed, the name of the current directory will be used.
87 |
88 | For example, `--name foo` will produce a binary `foo` on Linux, and `foo.exe` on Windows.
89 |
90 | #### `--out`
91 | Sets the output directory for the linker output. If no output directory is passed, the current directory will be used.
92 |
93 | #### `--type`
94 | Sets the type of the project, which can be an executable or a (dynamic) library. This is specified using the keywords `exe`, `dll`, or `lib`.
95 |
96 | For example, to create a dynamic library, you would pass `--type dll`.
97 |
98 | #### `--pkg`
99 | Adds a package to link to by its name. `qb` will try to resolve the package by itself, using a variety of sources. Listed here are the sources, in the order that they will be searched for:
100 |
101 | 1. **Local configuration**: If you have a `qb.toml` file, this will check for packages defined there.
102 | ```toml
103 | [package.sfml]
104 | includes = [ "D:\\Libs\\SFML-2.5.1\\include\\" ]
105 | linkdirs = [ "D:\\Libs\\SFML-2.5.1\\lib\\" ]
106 | links = [
107 | "sfml-main.lib",
108 | "sfml-graphics-s.lib",
109 | "sfml-system-s.lib",
110 | "sfml-window-s.lib",
111 | "opengl32.lib",
112 | "winmm.lib",
113 | "gdi32.lib",
114 | ]
115 | defines = [ "SFML_STATIC" ]
116 | ```
117 | 2. **pkgconfig**: If you have `pkg-config` installed on your system, it will be checking for packages from there.
118 | 3. Nothing else yet, but the following is planned: global configuration (like local, but system-wide), and vcpkg (for Windows).
119 |
120 | For example, to link with SFML, we can add `--pkg sfml`, as long as `sfml` can be resolved by one of the package sources.
121 |
122 | Additionally, if [Conan](https://conan.io/) is installed, it may be used as a way to manage packages. If a `conanfile.txt` exists, it will run `conan install .` (unless `conanbuildfile.txt` already exists). Then `conanbuildfile.txt` is used to properly compile & link to any dependencies in the Conanfile.
123 |
124 | #### `--static`
125 | Links statically in order to create a standalone binary that does not perform any loading of dynamic libraries.
126 |
127 | #### `--debug`
128 | Produces debug information for the resulting binary. On Windows that means a `.pdb` file, on Linux that means embedding debug information into the binary itself so that it can be used with gdb, and on Mac that means a `.dSYM` bundle.
129 |
130 | #### `--verbose`
131 | Makes it so that all compiler and linker commands will be printed to the log. Useful for debugging `qb` itself.
132 |
133 | #### `--strict`
134 | Makes the compiler more strict with its warnings.
135 |
136 | #### `--exceptions`
137 | Sets the way that the compiler's runtime will handle exceptions. Can either be `standard` (`std`), `all`, or `minimal` (`min`). The default is `standard`.
138 |
139 | This only makes a difference on Windows, where setting this to `all` will allow the runtime to catch certain access violation and other exceptions. When it's `minimal` or `min`, the minimal amount of exception handling will be done, which is similar to `all`, but there is no stack unwinding.
140 |
141 | #### `--optimize`
142 | Sets whether to use optimization. Can either be `default`, `none`, `size`, or `speed`. The default is `default`.
143 |
144 | When this option is set to `default`, whether the binary will be optimized is defined by whether it's a debug build or not. For example, when building with `qb --debug`, you will get an unoptimized binary, but by building without any options (by just running `qb`) it will produce an optimized build.
145 |
146 | #### `--cppstd`
147 | Sets which C++ standard to use. Can either be `latest`, `20`, `17`, or `14`. The default is `latest`.
148 |
149 | #### `--cstd`
150 | Sets which C standard to use. Can either be `latest`, `17`, or `11`. The default is `latest`.
151 |
152 | #### `--include`
153 | Adds a directory to the include path. For example, to add the folders `foo` and `bar` to the include path, you would run `qb --include foo --include bar`.
154 |
155 | #### `--define`
156 | Adds a precompiler definition. For example, to define `FOO` and `BAR` in the preprocessor when compiling, you would run `qb --define FOO --define BAR`.
157 |
158 | ### Configuration file
159 | It's possible to create a `qb.toml` file (in the folder you're running `qb`) to specify your configuration options as well. This is handy if you build a lot but don't want to pass the command line options every time.
160 |
161 | The configuration file works the same as the command line options, except they are in a toml file. Values not defined in the file will remain as their defaults. For example, to make `qb` always build as a dynamic library with the name `libfoo`, you would put this in `qb.toml`:
162 |
163 | ```toml
164 | name = "libfoo"
165 | type = "dll"
166 | ```
167 |
168 | To make a statically linked debug binary, you can put this in the configuration file:
169 |
170 | ```toml
171 | static = true
172 | debug = true
173 | ```
174 |
--------------------------------------------------------------------------------
/Release.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | rmdir /s /q Release
4 |
5 | set GOARCH=amd64
6 |
7 | set GOOS=windows
8 | go build -ldflags "-s" -o Release/Windows/qb.exe
9 |
10 | set GOOS=linux
11 | go build -ldflags "-s" -o Release/Linux/qb
12 |
13 | set GOOS=darwin
14 | go build -ldflags "-s" -o Release/MacOS/qb
15 |
--------------------------------------------------------------------------------
/compiler.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "path"
6 | "path/filepath"
7 | "runtime"
8 | "strings"
9 |
10 | "github.com/codecat/go-libs/log"
11 | )
12 |
13 | // LinkType specifies the output build type.
14 | type LinkType int
15 |
16 | const (
17 | // LinkExe will create an executable application. Will add the ".exe" suffix on Windows.
18 | LinkExe LinkType = iota
19 |
20 | // LinkDll will create a dynamic library. Will add the ".dll" suffix on Windows, and the ".so" suffix on Linux.
21 | LinkDll
22 |
23 | // LinkLib will create a static library. Will add the ".lib" suffix on Windows, and the ".a" suffix on Linux.
24 | LinkLib
25 | )
26 |
27 | // Compiler contains information about the compiler.
28 | type Compiler interface {
29 | Compile(path, objDir string, options *CompilerOptions) error
30 | Link(objDir, outPath string, outType LinkType, options *CompilerOptions) (string, error)
31 | Clean(name string)
32 | }
33 |
34 | // ExceptionType is the way that a compiler's runtime might handle exceptions.
35 | type ExceptionType int
36 |
37 | const (
38 | // ExceptionsStandard is the standard way of handling exceptions, and will perform stack unwinding.
39 | ExceptionsStandard ExceptionType = iota
40 |
41 | // ExceptionsAll is only supported on Windows, and will allow catching excptions such as access violations and integer divide by zero exceptions.
42 | ExceptionsAll
43 |
44 | // ExceptionsMinimal is only supported on Windows, and is similar to ExceptionsAll, except there is no stack unwinding.
45 | ExceptionsMinimal
46 | )
47 |
48 | // OptimizeType defines the compiler optimization type.
49 | type OptimizeType int
50 |
51 | const (
52 | // OptimizeDefault favors speed for release builds, and disables optimization for debug builds.
53 | OptimizeDefault OptimizeType = iota
54 |
55 | // OptimizeNone performs no optimization at all.
56 | OptimizeNone
57 |
58 | // OptimizeSize favors size over speed in optimization.
59 | OptimizeSize
60 |
61 | // OptimizeSpeed favors sped over size in optimization.
62 | OptimizeSpeed
63 | )
64 |
65 | // CPPStandardType defines the C++ standard to use.
66 | type CPPStandardType int
67 |
68 | const (
69 | // CPPStandardLatest uses the latest C++ standard available to the compiler.
70 | CPPStandardLatest CPPStandardType = iota
71 |
72 | // CPPStandard20 uses the C++20 standard.
73 | CPPStandard20
74 |
75 | // CPPStandard17 uses the C++17 standard.
76 | CPPStandard17
77 |
78 | // CPPStandard14 uses the C++14 standard.
79 | CPPStandard14
80 | )
81 |
82 | // CStandardType defines the C standard to use.
83 | type CStandardType int
84 |
85 | const (
86 | // CStandardLatest uses the latest C standard available to the compiler.
87 | CStandardLatest CStandardType = iota
88 |
89 | // CStandard11 uses the C17 standard.
90 | CStandard17
91 |
92 | // CStandard11 uses the C11 standard.
93 | CStandard11
94 | )
95 |
96 | // CompilerOptions contains options used for compiling and linking.
97 | type CompilerOptions struct {
98 | // Static sets whether to build a completely-static binary (eg. no dynamic link libraries are loaded from disk).
99 | Static bool
100 |
101 | // Debug configurations will add debug symbols. This will create a pdb file on Windows, and embed debugging information on Linux.
102 | Debug bool
103 |
104 | // Verbose compiling means we'll print the actual compiler and linker commands being executed.
105 | Verbose bool
106 |
107 | // Strict sets whether to be more strict on warnings.
108 | Strict bool
109 |
110 | // Include paths and library links
111 | IncludeDirectories []string
112 | LinkDirectories []string
113 | LinkLibraries []string
114 |
115 | // Additional compiler defines
116 | Defines []string
117 |
118 | // Additional compiler and linker flags
119 | CompilerFlagsCXX []string
120 | CompilerFlagsCPP []string
121 | CompilerFlagsC []string
122 | LinkerFlags []string
123 |
124 | // Specific options
125 | Exceptions ExceptionType
126 | Optimization OptimizeType
127 | CPPStandard CPPStandardType
128 | CStandard CStandardType
129 | }
130 |
131 | // CompilerWorkerTask describes a task for the compiler worker
132 | type CompilerWorkerTask struct {
133 | path string
134 | outputDir string
135 | }
136 |
137 | func compileWorker(ctx *Context, num int) {
138 | for {
139 | // Get a task
140 | task, ok := <-ctx.CompilerWorkerChannel
141 | if !ok {
142 | break
143 | }
144 |
145 | // Log the file we're currently compiling
146 | fileForward := strings.Replace(task.path, "\\", "/", -1)
147 | log.Info("%s", fileForward)
148 |
149 | // Invoke the compiler
150 | err := ctx.Compiler.Compile(task.path, task.outputDir, ctx.CompilerOptions)
151 | if err != nil {
152 | log.Error("Failed to compile %s!\n%s", fileForward, err.Error())
153 | ctx.CompilerErrors++
154 | }
155 | }
156 |
157 | ctx.CompilerWorkerFinished <- num
158 | }
159 |
160 | func performCompilation(ctx *Context) {
161 | // Prepare worker channels
162 | ctx.CompilerWorkerChannel = make(chan CompilerWorkerTask)
163 | ctx.CompilerWorkerFinished = make(chan int)
164 |
165 | // Start compiler worker routines
166 | numWorkers := runtime.NumCPU()
167 | if len(ctx.SourceFiles) < numWorkers {
168 | numWorkers = len(ctx.SourceFiles)
169 | }
170 | for i := 0; i < numWorkers; i++ {
171 | go compileWorker(ctx, i)
172 | }
173 |
174 | // Compile all the source files
175 | for _, file := range ctx.SourceFiles {
176 | // The output dir will be a sub-folder in the object directory
177 | dir := filepath.Dir(file)
178 | outputDir := filepath.Join(ctx.ObjectPath, dir)
179 |
180 | err := os.MkdirAll(outputDir, 0777)
181 | if err != nil {
182 | log.Error("Unable to create output directory %s: %s", outputDir, err.Error())
183 | ctx.CompilerErrors++
184 | continue
185 | }
186 |
187 | // Send the task to an available worker
188 | ctx.CompilerWorkerChannel <- CompilerWorkerTask{
189 | path: file,
190 | outputDir: outputDir,
191 | }
192 | }
193 |
194 | // Close the worker channel
195 | close(ctx.CompilerWorkerChannel)
196 |
197 | // Wait for all workers to finish compiling
198 | for i := 0; i < numWorkers; i++ {
199 | <-ctx.CompilerWorkerFinished
200 | }
201 | }
202 |
203 | func performLinking(ctx *Context) (string, error) {
204 | // Invoke the linker
205 | return ctx.Compiler.Link(ctx.ObjectPath, path.Join(ctx.OutPath, ctx.Name), ctx.Type, ctx.CompilerOptions)
206 | }
207 |
--------------------------------------------------------------------------------
/compiler_darwin.go:
--------------------------------------------------------------------------------
1 | //go:build darwin
2 |
3 | package main
4 |
5 | import (
6 | "errors"
7 | "os"
8 | "os/exec"
9 | "path/filepath"
10 | "strings"
11 |
12 | "github.com/codecat/go-libs/log"
13 | )
14 |
15 | type darwinCompiler struct {
16 | }
17 |
18 | func (ci darwinCompiler) Compile(path, objDir string, options *CompilerOptions) error {
19 | fileext := filepath.Ext(path)
20 | filename := strings.TrimSuffix(filepath.Base(path), fileext)
21 |
22 | args := make([]string, 0)
23 | args = append(args, "-c")
24 | args = append(args, "-o", filepath.Join(objDir, filename+".o"))
25 | args = append(args, "-std=c++17") // c++2a
26 |
27 | // Set warnings flags
28 | if options.Strict {
29 | args = append(args, "-Wall")
30 | args = append(args, "-Wextra")
31 | args = append(args, "-Werror")
32 | }
33 |
34 | // Set debug flag
35 | if options.Debug {
36 | args = append(args, "-g")
37 | }
38 |
39 | // Add optimization flags
40 | if options.Optimization == OptimizeSize {
41 | args = append(args, "-Os")
42 | } else if options.Optimization == OptimizeSpeed {
43 | args = append(args, "-O3")
44 | }
45 |
46 | // Add C++ standard flag
47 | if fileext != ".c" {
48 | switch options.CPPStandard {
49 | case CPPStandardLatest:
50 | args = append(args, "-std=c++2b")
51 | case CPPStandard20:
52 | args = append(args, "-std=c++20")
53 | case CPPStandard17:
54 | args = append(args, "-std=c++17")
55 | case CPPStandard14:
56 | args = append(args, "-std=c++14")
57 | }
58 | }
59 |
60 | // Add C standard flag
61 | if fileext == ".c" {
62 | switch options.CStandard {
63 | case CStandardLatest:
64 | args = append(args, "-std=c2x")
65 | case CStandard17:
66 | args = append(args, "-std=c17")
67 | case CStandard11:
68 | args = append(args, "-std=c11")
69 | }
70 | }
71 |
72 | // Add include directories
73 | for _, dir := range options.IncludeDirectories {
74 | args = append(args, "-I"+dir)
75 | }
76 |
77 | // Add precompiler definitions
78 | for _, define := range options.Defines {
79 | args = append(args, "-D"+define)
80 | }
81 |
82 | // Add additional compiler flags for C/C++
83 | args = append(args, options.CompilerFlagsCXX...)
84 |
85 | // Add additional compiler flags for C++
86 | if fileext != ".c" {
87 | args = append(args, options.CompilerFlagsCPP...)
88 | }
89 |
90 | // Add additional compiler flags for C
91 | if fileext == ".c" {
92 | args = append(args, options.CompilerFlagsC...)
93 | }
94 |
95 | args = append(args, path)
96 |
97 | cmd := exec.Command("clang", args...)
98 |
99 | if options.Verbose {
100 | log.Trace("%s", strings.Join(cmd.Args, " "))
101 | }
102 |
103 | outputBytes, err := cmd.CombinedOutput()
104 | if err != nil {
105 | output := strings.Trim(string(outputBytes), "\r\n")
106 | return errors.New(output)
107 | }
108 | return nil
109 | }
110 |
111 | func (ci darwinCompiler) Link(objDir, outPath string, outType LinkType, options *CompilerOptions) (string, error) {
112 | args := make([]string, 0)
113 |
114 | exeName := "clang"
115 |
116 | switch outType {
117 | case LinkExe:
118 | case LinkDll:
119 | outPath += ".dylib"
120 | args = append(args, "-dynamiclib")
121 | case LinkLib:
122 | exeName = "ar"
123 | outPath += ".a"
124 | }
125 |
126 | if outType == LinkLib {
127 | // r = insert with replacement
128 | // c = create new archie
129 | // s = write an index
130 | args = append(args, "rcs")
131 | args = append(args, outPath)
132 |
133 | } else {
134 | args = append(args, "-o", outPath)
135 |
136 | if options.Static {
137 | args = append(args, "-static")
138 | log.Warn("Static linking is not supported on MacOS!")
139 | }
140 |
141 | // Add additional library paths
142 | for _, dir := range options.LinkDirectories {
143 | args = append(args, "-L"+dir)
144 | }
145 |
146 | // Link to some common standard libraries
147 | args = append(args, "-lstdc++")
148 |
149 | // Add libraries to link
150 | for _, link := range options.LinkLibraries {
151 | args = append(args, "-l"+link)
152 | }
153 |
154 | // Add additional linker flags
155 | args = append(args, options.LinkerFlags...)
156 | }
157 |
158 | filepath.Walk(objDir, func(path string, info os.FileInfo, err error) error {
159 | if err != nil {
160 | return err
161 | }
162 | if info.IsDir() || !strings.HasSuffix(path, ".o") {
163 | return nil
164 | }
165 | args = append(args, path)
166 | return nil
167 | })
168 |
169 | cmd := exec.Command(exeName, args...)
170 |
171 | if options.Verbose {
172 | log.Trace("%s", strings.Join(cmd.Args, " "))
173 | }
174 |
175 | outputBytes, err := cmd.CombinedOutput()
176 | if err != nil {
177 | output := strings.Trim(string(outputBytes), "\r\n")
178 | return "", errors.New(output)
179 | }
180 |
181 | if options.Debug {
182 | cmd = exec.Command("dsymutil", outPath)
183 | err := cmd.Run()
184 | if err != nil {
185 | log.Warn("Unable to generate debug information: %s", err.Error())
186 | }
187 | }
188 |
189 | return outPath, nil
190 | }
191 |
192 | func (ci darwinCompiler) Clean(name string) {
193 | os.Remove(name)
194 | os.Remove(name + ".dylib")
195 | os.Remove(name + ".a")
196 | os.RemoveAll(name + ".dSYM")
197 | }
198 |
--------------------------------------------------------------------------------
/compiler_linux.go:
--------------------------------------------------------------------------------
1 | //go:build linux
2 |
3 | package main
4 |
5 | import (
6 | "errors"
7 | "os"
8 | "os/exec"
9 | "path/filepath"
10 | "strings"
11 |
12 | "github.com/codecat/go-libs/log"
13 | )
14 |
15 | type linuxCompiler struct {
16 | toolset string
17 | }
18 |
19 | func (ci linuxCompiler) Compile(path, objDir string, options *CompilerOptions) error {
20 | fileext := filepath.Ext(path)
21 | filename := strings.TrimSuffix(filepath.Base(path), fileext)
22 |
23 | args := make([]string, 0)
24 | args = append(args, "-c")
25 | args = append(args, "-o", filepath.Join(objDir, filename+".o"))
26 | args = append(args, "-std=c++17")
27 |
28 | // Set warnings flags
29 | if options.Strict {
30 | args = append(args, "-Wall")
31 | args = append(args, "-Wextra")
32 | args = append(args, "-Werror")
33 | }
34 |
35 | // Set debug flag
36 | if options.Debug {
37 | args = append(args, "-g")
38 | }
39 |
40 | // Add optimization flags
41 | if options.Optimization == OptimizeSize {
42 | args = append(args, "-Os")
43 | } else if options.Optimization == OptimizeSpeed {
44 | args = append(args, "-O3")
45 | }
46 |
47 | // Add C++ standard flag
48 | if fileext != ".c" {
49 | switch options.CPPStandard {
50 | case CPPStandardLatest:
51 | args = append(args, "-std=c++23")
52 | case CPPStandard20:
53 | args = append(args, "-std=c++20")
54 | case CPPStandard17:
55 | args = append(args, "-std=c++17")
56 | case CPPStandard14:
57 | args = append(args, "-std=c++14")
58 | }
59 | }
60 |
61 | // Add C standard flag
62 | if fileext == ".c" {
63 | switch options.CStandard {
64 | case CStandardLatest:
65 | args = append(args, "-std=c2x")
66 | case CStandard17:
67 | args = append(args, "-std=c17")
68 | case CStandard11:
69 | args = append(args, "-std=c11")
70 | }
71 | }
72 |
73 | // Add include directories
74 | for _, dir := range options.IncludeDirectories {
75 | args = append(args, "-I"+dir)
76 | }
77 |
78 | // Add precompiler definitions
79 | for _, define := range options.Defines {
80 | args = append(args, "-D"+define)
81 | }
82 |
83 | // Add additional compiler flags for C/C++
84 | args = append(args, options.CompilerFlagsCXX...)
85 |
86 | // Add additional compiler flags for C++
87 | if fileext != ".c" {
88 | args = append(args, options.CompilerFlagsCPP...)
89 | }
90 |
91 | // Add additional compiler flags for C
92 | if fileext == ".c" {
93 | args = append(args, options.CompilerFlagsC...)
94 | }
95 |
96 | args = append(args, path)
97 |
98 | cmd := exec.Command(ci.toolset, args...)
99 |
100 | if options.Verbose {
101 | log.Trace("%s", strings.Join(cmd.Args, " "))
102 | }
103 |
104 | outputBytes, err := cmd.CombinedOutput()
105 | if err != nil {
106 | output := strings.Trim(string(outputBytes), "\r\n")
107 | return errors.New(output)
108 | }
109 | return nil
110 | }
111 |
112 | func (ci linuxCompiler) Link(objDir, outPath string, outType LinkType, options *CompilerOptions) (string, error) {
113 | args := make([]string, 0)
114 |
115 | exeName := ci.toolset
116 |
117 | switch outType {
118 | case LinkExe:
119 | case LinkDll:
120 | outPath += ".so"
121 | args = append(args, "-shared")
122 | case LinkLib:
123 | exeName = "ar"
124 | outPath += ".a"
125 | }
126 |
127 | if outType == LinkLib {
128 | // r = insert with replacement
129 | // c = create new archie
130 | // s = write an index
131 | args = append(args, "rcs")
132 | args = append(args, outPath)
133 |
134 | } else {
135 | args = append(args, "-o", outPath)
136 |
137 | if ci.toolset == "gcc" {
138 | args = append(args, "-static-libgcc")
139 | args = append(args, "-static-libstdc++")
140 | }
141 |
142 | if options.Static {
143 | args = append(args, "-static")
144 | }
145 |
146 | // Add additional library paths
147 | for _, dir := range options.LinkDirectories {
148 | args = append(args, "-L"+dir)
149 | }
150 | }
151 |
152 | filepath.Walk(objDir, func(path string, info os.FileInfo, err error) error {
153 | if err != nil {
154 | return err
155 | }
156 | if info.IsDir() || !strings.HasSuffix(path, ".o") {
157 | return nil
158 | }
159 | args = append(args, path)
160 | return nil
161 | })
162 |
163 | if outType != LinkLib {
164 | // Link to some common standard libraries
165 | args = append(args, "-lstdc++")
166 |
167 | // Add libraries to link
168 | for _, link := range options.LinkLibraries {
169 | args = append(args, "-l"+link)
170 | }
171 |
172 | // Add additional linker flags
173 | args = append(args, options.LinkerFlags...)
174 | }
175 |
176 | cmd := exec.Command(exeName, args...)
177 |
178 | if options.Verbose {
179 | log.Trace("%s", strings.Join(cmd.Args, " "))
180 | }
181 |
182 | outputBytes, err := cmd.CombinedOutput()
183 | if err != nil {
184 | output := strings.Trim(string(outputBytes), "\r\n")
185 | return "", errors.New(output)
186 | }
187 | return outPath, nil
188 | }
189 |
190 | func (ci linuxCompiler) Clean(name string) {
191 | os.Remove(name)
192 | os.Remove(name + ".so")
193 | os.Remove(name + ".a")
194 | }
195 |
--------------------------------------------------------------------------------
/compiler_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | package main
4 |
5 | import (
6 | "errors"
7 | "fmt"
8 | "os"
9 | "os/exec"
10 | "path/filepath"
11 | "strings"
12 |
13 | "github.com/codecat/go-libs/log"
14 | )
15 |
16 | type windowsCompiler struct {
17 | installDir string
18 | installVersion string
19 |
20 | sdkDir string
21 | sdkVersion string
22 | }
23 |
24 | func (ci windowsCompiler) toolsDir() string {
25 | return filepath.Join(ci.installDir, "VC\\Tools\\MSVC", ci.installVersion)
26 | }
27 |
28 | func (ci windowsCompiler) binDir() string {
29 | return filepath.Join(ci.toolsDir(), "bin\\Hostx64\\x64")
30 | }
31 |
32 | func (ci windowsCompiler) sdkIncludeDir() string {
33 | return filepath.Join(ci.sdkDir, "include", ci.sdkVersion+".0")
34 | }
35 |
36 | func (ci windowsCompiler) sdkLibDir() string {
37 | return filepath.Join(ci.sdkDir, "lib", ci.sdkVersion+".0")
38 | }
39 |
40 | func (ci windowsCompiler) compiler() string {
41 | return filepath.Join(ci.binDir(), "cl.exe")
42 | }
43 |
44 | func (ci windowsCompiler) linker() string {
45 | return filepath.Join(ci.binDir(), "link.exe")
46 | }
47 |
48 | func (ci windowsCompiler) libber() string {
49 | return filepath.Join(ci.binDir(), "lib.exe")
50 | }
51 |
52 | func (ci windowsCompiler) includeDirs() []string {
53 | ret := make([]string, 0)
54 |
55 | // MSVC includes
56 | ret = append(ret, filepath.Join(ci.toolsDir(), "ATLMFC\\include"))
57 | ret = append(ret, filepath.Join(ci.toolsDir(), "include"))
58 |
59 | // Windows Kit includes
60 | ret = append(ret, filepath.Join(ci.sdkIncludeDir(), "ucrt"))
61 | ret = append(ret, filepath.Join(ci.sdkIncludeDir(), "shared"))
62 | ret = append(ret, filepath.Join(ci.sdkIncludeDir(), "um"))
63 | ret = append(ret, filepath.Join(ci.sdkIncludeDir(), "winrt"))
64 | ret = append(ret, filepath.Join(ci.sdkIncludeDir(), "cppwinrt"))
65 |
66 | return ret
67 | }
68 |
69 | func (ci windowsCompiler) linkDirs() []string {
70 | ret := make([]string, 0)
71 |
72 | // MSVC libraries
73 | ret = append(ret, filepath.Join(ci.toolsDir(), "ATLMFC\\lib\\x64"))
74 | ret = append(ret, filepath.Join(ci.toolsDir(), "lib\\x64"))
75 |
76 | // Windows Kit libraries
77 | ret = append(ret, filepath.Join(ci.sdkLibDir(), "ucrt\\x64"))
78 | ret = append(ret, filepath.Join(ci.sdkLibDir(), "um\\x64"))
79 |
80 | return ret
81 | }
82 |
83 | func (ci windowsCompiler) Compile(path, objDir string, options *CompilerOptions) error {
84 | // cl.exe args: https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-by-category?view=msvc-170
85 |
86 | fileext := filepath.Ext(path)
87 | filename := strings.TrimSuffix(filepath.Base(path), fileext)
88 |
89 | args := make([]string, 0)
90 | args = append(args, "/nologo") // Suppress startup banner
91 | args = append(args, "/c") // Compile without linking
92 | args = append(args, "/GS") // Enables buffer security checks
93 | args = append(args, "/Qspectre") // Add instructions to mitigate Spectre variant 1 security vulnerabilities
94 | args = append(args, "/Zc:inline") // Remove unreferenced function or data if it is COMDAT or has internal linkage only
95 |
96 | // Warnings: command line default is /W1, Visual Studio default is /W3
97 | if options.Strict {
98 | args = append(args, "/W4") // Note: Using /Wall enables warnings like C4710 when using functions like printf, so we leave it at /W4
99 | args = append(args, "/WX") // Treats all warnings as errors
100 | } else {
101 | args = append(args, "/W3")
102 | }
103 |
104 | // Set object output path
105 | args = append(args, fmt.Sprintf("/Fo%s\\%s.obj", objDir, filename))
106 |
107 | // Define the runtime flag
108 | runtimeFlag := "/M"
109 | if options.Static {
110 | runtimeFlag += "T"
111 | } else {
112 | runtimeFlag += "D"
113 | }
114 | if options.Debug {
115 | runtimeFlag += "d"
116 | }
117 | args = append(args, runtimeFlag)
118 |
119 | // Add exception handling flags in C++
120 | if fileext != ".c" {
121 | if options.Exceptions == ExceptionsStandard {
122 | args = append(args, "/EHsc")
123 | } else if options.Exceptions == ExceptionsAll {
124 | args = append(args, "/EHa")
125 | } else if options.Exceptions == ExceptionsMinimal {
126 | args = append(args, "/EH")
127 | }
128 | }
129 |
130 | // Add optimization flags
131 | if options.Optimization == OptimizeSize {
132 | args = append(args, "/O1")
133 | } else if options.Optimization == OptimizeSpeed {
134 | args = append(args, "/O2")
135 | }
136 |
137 | // Add C++ standard flag
138 | if fileext != ".c" {
139 | switch options.CPPStandard {
140 | case CPPStandardLatest:
141 | args = append(args, "/std:c++latest")
142 | case CPPStandard20:
143 | args = append(args, "/std:c++20")
144 | case CPPStandard17:
145 | args = append(args, "/std:c++17")
146 | case CPPStandard14:
147 | args = append(args, "/std:c++14")
148 | }
149 | }
150 |
151 | // Add C standard flag
152 | if fileext == ".c" {
153 | switch options.CStandard {
154 | case CStandardLatest:
155 | args = append(args, "/std:clatest")
156 | case CStandard17:
157 | args = append(args, "/std:c17")
158 | case CStandard11:
159 | args = append(args, "/std:c11")
160 | }
161 | }
162 |
163 | // Add include directories
164 | for _, dir := range options.IncludeDirectories {
165 | args = append(args, "/I"+dir)
166 | }
167 |
168 | // Add precompiler definitions
169 | for _, define := range options.Defines {
170 | args = append(args, "/D"+define)
171 | }
172 |
173 | // Add additional compiler flags for C/C++
174 | args = append(args, options.CompilerFlagsCXX...)
175 |
176 | // Add additional compiler flags for C++
177 | if fileext != ".c" {
178 | args = append(args, options.CompilerFlagsCPP...)
179 | }
180 |
181 | // Add additional compiler flags for C
182 | if fileext == ".c" {
183 | args = append(args, options.CompilerFlagsC...)
184 | }
185 |
186 | args = append(args, path)
187 |
188 | cmd := exec.Command(ci.compiler(), args...)
189 | cmd.Env = append(os.Environ(),
190 | "INCLUDE="+strings.Join(ci.includeDirs(), ";"),
191 | )
192 |
193 | if options.Verbose {
194 | log.Trace("%s", strings.Join(cmd.Args, " "))
195 | }
196 |
197 | outputBytes, err := cmd.CombinedOutput()
198 | if err != nil {
199 | output := strings.Trim(string(outputBytes), "\r\n")
200 | // Skip the first line from the output as it will always be the filename
201 | lines := strings.SplitN(output, "\n", 2)
202 | return errors.New(lines[1])
203 | }
204 | return nil
205 | }
206 |
207 | func (ci windowsCompiler) Link(objDir, outPath string, outType LinkType, options *CompilerOptions) (string, error) {
208 | // link.exe args: https://learn.microsoft.com/en-us/cpp/build/reference/linker-options?view=msvc-170
209 |
210 | exeName := ci.linker()
211 |
212 | args := make([]string, 0)
213 | args = append(args, "/nologo")
214 | args = append(args, "/machine:x64")
215 | args = append(args, "/incremental:no")
216 |
217 | if options.Debug {
218 | args = append(args, "/debug")
219 | }
220 |
221 | switch outType {
222 | case LinkExe:
223 | outPath += ".exe"
224 |
225 | case LinkDll:
226 | outPath += ".dll"
227 | args = append(args, "/dll")
228 |
229 | case LinkLib:
230 | exeName = ci.libber()
231 | outPath += ".lib"
232 | args = append(args, "/lib")
233 | }
234 |
235 | args = append(args, "/out:"+outPath)
236 |
237 | // Add additional library paths
238 | for _, dir := range options.LinkDirectories {
239 | args = append(args, "/libpath:"+dir)
240 | }
241 |
242 | // Add libraries to link
243 | args = append(args, options.LinkLibraries...)
244 |
245 | // Add additional linker flags
246 | args = append(args, options.LinkerFlags...)
247 |
248 | // Link to some common standard libraries
249 | args = append(args, "kernel32.lib")
250 | args = append(args, "user32.lib")
251 | args = append(args, "shell32.lib")
252 | args = append(args, "advapi32.lib")
253 |
254 | filepath.Walk(objDir, func(path string, info os.FileInfo, err error) error {
255 | if err != nil {
256 | return err
257 | }
258 | if info.IsDir() || !strings.HasSuffix(path, ".obj") {
259 | return nil
260 | }
261 | args = append(args, path)
262 | return nil
263 | })
264 |
265 | cmd := exec.Command(exeName, args...)
266 | cmd.Env = append(os.Environ(),
267 | "LIB="+strings.Join(ci.linkDirs(), ";"),
268 | )
269 |
270 | if options.Verbose {
271 | log.Trace("%s", strings.Join(cmd.Args, " "))
272 | }
273 |
274 | outputBytes, err := cmd.CombinedOutput()
275 | if err != nil {
276 | output := strings.Trim(string(outputBytes), "\r\n")
277 | return "", errors.New(output)
278 | }
279 | return outPath, nil
280 | }
281 |
282 | func (ci windowsCompiler) Clean(name string) {
283 | os.Remove(name + ".exe")
284 | os.Remove(name + ".dll")
285 | os.Remove(name + ".lib")
286 | os.Remove(name + ".pdb")
287 | }
288 |
--------------------------------------------------------------------------------
/conan.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "os"
7 | "regexp"
8 | "runtime"
9 | "strings"
10 |
11 | "github.com/codecat/go-libs/log"
12 | )
13 |
14 | type Conanfile map[string][]string
15 |
16 | func loadConanFile(path string) (Conanfile, error) {
17 | f, err := os.Open(path)
18 | if err != nil {
19 | return nil, err
20 | }
21 |
22 | regexHeader := regexp.MustCompile(`^\[(.*)\]$`)
23 | currentHeader := ""
24 |
25 | ret := make(Conanfile)
26 |
27 | scanner := bufio.NewScanner(f)
28 | for scanner.Scan() {
29 | line := scanner.Text()
30 |
31 | if line == "" {
32 | continue
33 | }
34 |
35 | res := regexHeader.FindStringSubmatch(line)
36 | if len(res) == 0 {
37 | if currentHeader == "" {
38 | return nil, fmt.Errorf("invalid conanfile on line: \"%s\"", line)
39 | }
40 | ret[currentHeader] = append(ret[currentHeader], strings.Trim(line, " \t"))
41 | } else {
42 | currentHeader = res[1]
43 | }
44 | }
45 |
46 | return ret, nil
47 | }
48 |
49 | func addConanPackages(ctx *Context, conan Conanfile) {
50 | //TODO: Implement all Conan features
51 | // conan["frameworkdirs"] // contains .framework files, only needed on MacOS
52 | // conan["frameworks"] // frameworks to link to
53 | // conan["bindirs"] // contains .dll files, only needed when linking with shared libraries
54 |
55 | log.Info("Adding Conan packages")
56 |
57 | isWindows := runtime.GOOS == "windows"
58 |
59 | // contains .h files
60 | ctx.CompilerOptions.IncludeDirectories = append(ctx.CompilerOptions.IncludeDirectories, conan["includedirs"]...)
61 |
62 | // contains .lib files
63 | ctx.CompilerOptions.LinkDirectories = append(ctx.CompilerOptions.LinkDirectories, conan["libdirs"]...)
64 |
65 | // libraries to link to
66 | for _, lib := range conan["libs"] {
67 | if isWindows && !strings.HasSuffix(lib, ".lib") {
68 | lib += ".lib"
69 | }
70 | ctx.CompilerOptions.LinkLibraries = append(ctx.CompilerOptions.LinkLibraries, lib)
71 | }
72 |
73 | // additional system libraries to link to
74 | for _, lib := range conan["system_libs"] {
75 | if isWindows && !strings.HasSuffix(lib, ".lib") {
76 | lib += ".lib"
77 | }
78 | ctx.CompilerOptions.LinkLibraries = append(ctx.CompilerOptions.LinkLibraries, lib)
79 | }
80 |
81 | // precompiler defines to add
82 | ctx.CompilerOptions.Defines = append(ctx.CompilerOptions.Defines, conan["defines"]...)
83 |
84 | // C++ compiler flags to add
85 | ctx.CompilerOptions.CompilerFlagsCPP = append(ctx.CompilerOptions.CompilerFlagsCPP, conan["cppflags"]...)
86 |
87 | // C/C++ compiler flags to add
88 | ctx.CompilerOptions.CompilerFlagsCXX = append(ctx.CompilerOptions.CompilerFlagsCXX, conan["cxxflags"]...)
89 |
90 | // C compiler flags to add
91 | ctx.CompilerOptions.CompilerFlagsC = append(ctx.CompilerOptions.CompilerFlagsC, conan["cflags"]...)
92 |
93 | if ctx.Type == LinkDll {
94 | // linker flags to add when building a shared library
95 | ctx.CompilerOptions.LinkerFlags = append(ctx.CompilerOptions.LinkerFlags, conan["sharedlinkflags"]...)
96 |
97 | } else if ctx.Type == LinkExe {
98 | // linker flags to add when building an executable
99 | ctx.CompilerOptions.LinkerFlags = append(ctx.CompilerOptions.LinkerFlags, conan["exelinkflags"]...)
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/context.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Context contains all the build system states that have to be remembered.
4 | type Context struct {
5 | // Name is the name of the project.
6 | Name string
7 |
8 | // Final binary type we want to link.
9 | Type LinkType
10 |
11 | // SourceFiles contains paths to all the .c and .cpp files that have to be compiled.
12 | SourceFiles []string
13 |
14 | // ObjectPath is the intermediate folder where object files should be stored.
15 | ObjectPath string
16 |
17 | // OutPath is the directory where the final binary is written to.
18 | OutPath string
19 |
20 | // Compiler is an abstract interface used for compiling and linking on multiple platforms.
21 | Compiler Compiler
22 | CompilerErrors int
23 | CompilerOptions *CompilerOptions
24 | CompilerWorkerChannel chan CompilerWorkerTask
25 | CompilerWorkerFinished chan int
26 | }
27 |
28 | // NewContext creates a new context with initial values.
29 | func NewContext() (*Context, error) {
30 | compiler, err := getCompiler()
31 | if err != nil {
32 | return nil, err
33 | }
34 |
35 | return &Context{
36 | Compiler: compiler,
37 | CompilerOptions: &CompilerOptions{},
38 |
39 | SourceFiles: make([]string, 0),
40 | }, nil
41 | }
42 |
--------------------------------------------------------------------------------
/fileexists.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "os"
4 |
5 | func fileExists(path string) bool {
6 | info, err := os.Stat(path)
7 | if os.IsNotExist(err) {
8 | return false
9 | }
10 | return !info.IsDir()
11 | }
12 |
--------------------------------------------------------------------------------
/getcompiler_darwin.go:
--------------------------------------------------------------------------------
1 | // +build darwin
2 |
3 | package main
4 |
5 | func getCompiler() (Compiler, error) {
6 | return darwinCompiler{}, nil
7 | }
8 |
--------------------------------------------------------------------------------
/getcompiler_linux.go:
--------------------------------------------------------------------------------
1 | // +build linux
2 |
3 | package main
4 |
5 | import (
6 | "errors"
7 | "os/exec"
8 | )
9 |
10 | func getCompiler() (Compiler, error) {
11 | //TODO: Check if we have gcc and ld installed
12 | //TODO: Add option for other flavors of gcc (eg. mingw)
13 |
14 | toolset := ""
15 |
16 | if _, err := exec.LookPath("clang"); err == nil {
17 | toolset = "clang"
18 | } else if _, err := exec.LookPath("gcc"); err == nil {
19 | toolset = "gcc"
20 | } else {
21 | return nil, errors.New("couldn't find clang or gcc in the PATH")
22 | }
23 |
24 | return linuxCompiler{
25 | toolset: toolset,
26 | }, nil
27 | }
28 |
--------------------------------------------------------------------------------
/getcompiler_windows.go:
--------------------------------------------------------------------------------
1 | // +build windows
2 |
3 | package main
4 |
5 | import (
6 | "encoding/json"
7 | "errors"
8 | "io/ioutil"
9 | "os/exec"
10 | "path/filepath"
11 | "strings"
12 |
13 | "golang.org/x/sys/windows/registry"
14 | )
15 |
16 | type vswhereOutput struct {
17 | InstanceID string `json:"instanceId"`
18 | InstallPath string `json:"installationPath"`
19 | }
20 |
21 | func getCompiler() (Compiler, error) {
22 | ret := windowsCompiler{}
23 |
24 | vswherePath := "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe"
25 | if !fileExists(vswherePath) {
26 | return nil, errors.New("couldn't find vswhere in the default path")
27 | }
28 |
29 | cmd := exec.Command(vswherePath, "-latest", "-format", "json")
30 | output, err := cmd.Output()
31 | if err != nil {
32 | return nil, err
33 | }
34 |
35 | var outputs []vswhereOutput
36 | json.Unmarshal(output, &outputs)
37 |
38 | if len(outputs) == 0 {
39 | return nil, errors.New("vswhere didn't return any installations")
40 | }
41 |
42 | ret.installDir = outputs[0].InstallPath
43 | installVersionBytes, err := ioutil.ReadFile(filepath.Join(ret.installDir, "VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"))
44 | if err != nil {
45 | return nil, errors.New("unable to open Microsoft.VCToolsVersion.default.txt")
46 | }
47 |
48 | ret.installVersion = strings.Trim(string(installVersionBytes), "\r\n")
49 |
50 | // Get Windows 10 SDK path
51 | // HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\Windows\v10.0
52 | keySDK, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\WOW6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0", registry.QUERY_VALUE)
53 | if err != nil {
54 | return nil, err
55 | }
56 |
57 | ret.sdkDir, _, _ = keySDK.GetStringValue("InstallationFolder")
58 | ret.sdkVersion, _, _ = keySDK.GetStringValue("ProductVersion")
59 |
60 | return ret, nil
61 | }
62 |
--------------------------------------------------------------------------------
/getsourcefiles.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "regexp"
7 | )
8 |
9 | func getSourceFiles() ([]string, error) {
10 | ret := make([]string, 0)
11 | err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
12 | if err != nil {
13 | return err
14 | }
15 |
16 | if info.IsDir() {
17 | return nil
18 | }
19 |
20 | if ok, _ := regexp.Match("\\.(cpp|c)$", []byte(path)); !ok {
21 | return nil
22 | }
23 |
24 | ret = append(ret, path)
25 | return nil
26 | })
27 | return ret, err
28 | }
29 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/codecat/qb
2 |
3 | go 1.22
4 |
5 | require (
6 | github.com/codecat/go-libs v0.0.0-20210906174629-ffa6674c8e05
7 | github.com/mattn/go-shellwords v1.0.12
8 | github.com/spf13/pflag v1.0.5
9 | github.com/spf13/viper v1.18.2
10 | golang.org/x/sys v0.17.0
11 | )
12 |
13 | require (
14 | github.com/fatih/color v1.16.0 // indirect
15 | github.com/fsnotify/fsnotify v1.7.0 // indirect
16 | github.com/hashicorp/hcl v1.0.0 // indirect
17 | github.com/magiconair/properties v1.8.7 // indirect
18 | github.com/mattn/go-colorable v0.1.13 // indirect
19 | github.com/mattn/go-isatty v0.0.20 // indirect
20 | github.com/mitchellh/mapstructure v1.5.0 // indirect
21 | github.com/pelletier/go-toml/v2 v2.1.1 // indirect
22 | github.com/sagikazarmark/locafero v0.4.0 // indirect
23 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect
24 | github.com/sourcegraph/conc v0.3.0 // indirect
25 | github.com/spf13/afero v1.11.0 // indirect
26 | github.com/spf13/cast v1.6.0 // indirect
27 | github.com/subosito/gotenv v1.6.0 // indirect
28 | go.uber.org/multierr v1.11.0 // indirect
29 | golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
30 | golang.org/x/text v0.14.0 // indirect
31 | gopkg.in/ini.v1 v1.67.0 // indirect
32 | gopkg.in/yaml.v3 v3.0.1 // indirect
33 | )
34 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/codecat/go-libs v0.0.0-20210906174629-ffa6674c8e05 h1:JSfDXHJvrIpQ8Agy//yoIlGpfIprTCDUytmf68fd/Lc=
2 | github.com/codecat/go-libs v0.0.0-20210906174629-ffa6674c8e05/go.mod h1:xJW98cHEb+Kbuu0qmoKzExh3blthZqojIYOFo27VgvE=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
6 | github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
7 | github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
8 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
9 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
10 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
11 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
12 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
13 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
14 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
15 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
16 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
17 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
18 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
19 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
20 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
21 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
22 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
23 | github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
24 | github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
25 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
26 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
27 | github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
28 | github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
29 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
30 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
31 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
32 | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
33 | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
34 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
35 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
36 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
37 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
38 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
39 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
40 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
41 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
42 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
43 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
44 | github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
45 | github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
46 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
47 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
48 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
49 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
50 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
51 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
52 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
53 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
54 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
55 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
56 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
57 | golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
58 | golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
59 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
60 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
61 | golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
62 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
63 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
64 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
65 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
66 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
67 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
68 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
69 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
70 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
71 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
72 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "os/exec"
7 | "path/filepath"
8 | "slices"
9 | "time"
10 |
11 | "github.com/codecat/go-libs/log"
12 | "github.com/spf13/pflag"
13 | "github.com/spf13/viper"
14 | )
15 |
16 | func hasCommand(cmd string) bool {
17 | for _, arg := range pflag.Args() {
18 | if arg == cmd {
19 | return true
20 | }
21 | }
22 | return false
23 | }
24 |
25 | func main() {
26 | // Configure logging
27 | log.CurrentConfig.Category = false
28 |
29 | // Prepare possible command line flags
30 | pflag.String("name", "", "binary output name without the extension")
31 | pflag.String("out", "", "the output directory")
32 | pflag.String("type", "exe", "binary output type, either \"exe\", \"dll\", or \"lib\"")
33 | pflag.Bool("static", false, "link statically to create a standalone binary")
34 | pflag.Bool("debug", false, "produce debug information")
35 | pflag.Bool("verbose", false, "print all compiler and linker commands being executed")
36 | pflag.Bool("strict", false, "be more strict in compiler warnings")
37 | pflag.String("exceptions", "std", "way to handle exceptions, either \"std\", \"all\", or \"min\"")
38 | pflag.String("optimize", "default", "enable optimizations, either \"defualt\", \"none\", \"size\", or \"speed\"")
39 | pflag.String("cppstd", "latest", "select the C++ standard to use, either \"latest\", \"20\", \"17\", or \"14\"")
40 | pflag.String("cstd", "latest", "select the C standard to use, either \"latest\", \"17\", or \"11\"")
41 | pflag.StringSlice("include", nil, "directories to add to the include path")
42 | pflag.StringSlice("define", nil, "adds a precompiler definition")
43 | pflag.StringSlice("pkg", nil, "packages to link for compilation")
44 | pflag.Parse()
45 |
46 | // Load a qb.toml file, if it exists
47 | viper.AddConfigPath(".")
48 | viper.SetConfigName("qb")
49 | viper.BindPFlags(pflag.CommandLine)
50 | err := viper.ReadInConfig()
51 | if err == nil {
52 | log.Info("Using build configuration file %s", filepath.Base(viper.ConfigFileUsed()))
53 | }
54 |
55 | // Prepare qb's internal context
56 | ctx, err := NewContext()
57 | if err != nil {
58 | log.Fatal("Unable to initialize context: %s", err.Error())
59 | os.Exit(1)
60 | }
61 |
62 | // Find the name of the project
63 | ctx.Name = viper.GetString("name")
64 | if ctx.Name == "" {
65 | // If there's no name set, use the name of the current directory
66 | currentDir, _ := filepath.Abs(".")
67 | ctx.Name = filepath.Base(currentDir)
68 | }
69 |
70 | // Get the output path
71 | ctx.OutPath = viper.GetString("out")
72 |
73 | // Get the link type
74 | switch viper.GetString("type") {
75 | case "exe":
76 | ctx.Type = LinkExe
77 | case "dll":
78 | ctx.Type = LinkDll
79 | case "lib":
80 | ctx.Type = LinkLib
81 | default:
82 | ctx.Type = LinkExe
83 | }
84 |
85 | // If we only have to clean, do that and exit
86 | if hasCommand("clean") {
87 | ctx.Compiler.Clean(ctx.Name)
88 | return
89 | }
90 |
91 | // Load any compiler options
92 | ctx.CompilerOptions.Static = viper.GetBool("static")
93 | ctx.CompilerOptions.Debug = viper.GetBool("debug")
94 | ctx.CompilerOptions.Verbose = viper.GetBool("verbose")
95 | ctx.CompilerOptions.Strict = viper.GetBool("strict")
96 |
97 | // Load the exceptions method
98 | exceptionsType := viper.GetString("exceptions")
99 | switch exceptionsType {
100 | case "", "std", "standard":
101 | ctx.CompilerOptions.Exceptions = ExceptionsStandard
102 | case "all":
103 | ctx.CompilerOptions.Exceptions = ExceptionsAll
104 | case "min", "minimal":
105 | ctx.CompilerOptions.Exceptions = ExceptionsMinimal
106 | default:
107 | log.Warn("Unrecognized exceptions type %s", exceptionsType)
108 | }
109 |
110 | // Load optimization options
111 | optimizeType := viper.GetString("optimize")
112 | switch optimizeType {
113 | case "", "default":
114 | ctx.CompilerOptions.Optimization = OptimizeDefault
115 | case "none":
116 | ctx.CompilerOptions.Optimization = OptimizeNone
117 | case "size":
118 | ctx.CompilerOptions.Optimization = OptimizeSize
119 | case "speed":
120 | ctx.CompilerOptions.Optimization = OptimizeSpeed
121 | default:
122 | log.Warn("Unrecognized optimization type %s", optimizeType)
123 | }
124 |
125 | // If we're on default optimization, optimize for speed if we're not a debug build
126 | if ctx.CompilerOptions.Optimization == OptimizeDefault && !ctx.CompilerOptions.Debug {
127 | ctx.CompilerOptions.Optimization = OptimizeSpeed
128 | }
129 |
130 | // Load C++ compiler standard
131 | cppStandard := viper.GetString("cppstd")
132 | switch cppStandard {
133 | case "", "latest":
134 | ctx.CompilerOptions.CPPStandard = CPPStandardLatest
135 | case "20":
136 | ctx.CompilerOptions.CPPStandard = CPPStandard20
137 | case "17":
138 | ctx.CompilerOptions.CPPStandard = CPPStandard17
139 | case "14":
140 | ctx.CompilerOptions.CPPStandard = CPPStandard14
141 | default:
142 | log.Warn("Unrecognized C++ compiler standard %s", cppStandard)
143 | }
144 |
145 | // Load C compiler standard
146 | cStandard := viper.GetString("cstd")
147 | switch cStandard {
148 | case "", "latest":
149 | ctx.CompilerOptions.CStandard = CStandardLatest
150 | case "17":
151 | ctx.CompilerOptions.CStandard = CStandard17
152 | case "11":
153 | ctx.CompilerOptions.CStandard = CStandard11
154 | default:
155 | log.Warn("Unrecognized C compiler standard %s", cStandard)
156 | }
157 |
158 | // Add custom include directories
159 | includes := viper.GetStringSlice("include")
160 | for _, include := range includes {
161 | fi, err := os.Stat(include)
162 | if err != nil {
163 | log.Warn("Unable to include directory %s: %s", include, err.Error())
164 | continue
165 | }
166 |
167 | if !fi.IsDir() {
168 | log.Warn("Include path is not a directory: %s", include)
169 | continue
170 | }
171 |
172 | ctx.CompilerOptions.IncludeDirectories = append(ctx.CompilerOptions.IncludeDirectories, include)
173 | }
174 |
175 | // Add preprocessor definitions
176 | defines := viper.GetStringSlice("define")
177 | ctx.CompilerOptions.Defines = append(ctx.CompilerOptions.Defines, defines...)
178 |
179 | // Find packages
180 | packages := viper.GetStringSlice("pkg")
181 | for _, pkg := range packages {
182 | pkgInfo := addPackage(ctx.CompilerOptions, pkg)
183 | if pkgInfo == nil {
184 | log.Warn("Unable to find package %s!", pkg)
185 | continue
186 | }
187 | }
188 |
189 | // To support Conan: run "conan install", if a conanfile exists, but conanbuildinfo.txt does not exist
190 | if fileExists("conanfile.txt") && !fileExists("conanbuildinfo.txt") {
191 | log.Info("Conanfile found: installing dependencies from Conan")
192 | err := exec.Command("conan", "install", ".").Run()
193 | if err != nil {
194 | log.Warn("Conan install failed: %s", err.Error())
195 | }
196 | }
197 |
198 | // To support Conan: use conanbuildinfo.txt, if it exists
199 | if fileExists("conanbuildinfo.txt") {
200 | conan, err := loadConanFile("conanbuildinfo.txt")
201 | if err != nil {
202 | log.Warn("Unable to load conanbuildinfo.txt: %s", err.Error())
203 | } else {
204 | addConanPackages(ctx, conan)
205 | }
206 | }
207 |
208 | // Find all the source files to compile
209 | ctx.SourceFiles, err = getSourceFiles()
210 | if err != nil {
211 | log.Fatal("Unable to read directory: %s", err.Error())
212 | os.Exit(1)
213 | }
214 |
215 | if len(ctx.SourceFiles) == 0 {
216 | log.Warn("No source files found!")
217 | os.Exit(1)
218 | }
219 |
220 | // Make a temporary folder for .obj files
221 | ctx.ObjectPath = filepath.Join(os.TempDir(), fmt.Sprintf("qb_%d", time.Now().Unix()))
222 | os.Mkdir(ctx.ObjectPath, 0777)
223 | defer os.RemoveAll(ctx.ObjectPath)
224 |
225 | // Perform the compilation
226 | timeStart := time.Now()
227 | performCompilation(ctx)
228 | timeCompilation := time.Since(timeStart)
229 |
230 | // Stop if there were any compiler errors
231 | if ctx.CompilerErrors > 0 {
232 | log.Fatal("😢 Compilation failed!")
233 | os.Exit(1)
234 | }
235 |
236 | // Perform the linking
237 | timeStart = time.Now()
238 | outPath, err := performLinking(ctx)
239 | timeLinking := time.Since(timeStart)
240 |
241 | // Stop if linking failed
242 | if err != nil {
243 | log.Fatal("😢 Link failed!")
244 | log.Fatal("%s", err.Error())
245 | os.Exit(1)
246 | }
247 |
248 | // Report succcess
249 | log.Info("👏 %s", outPath)
250 | log.Info("⏳ compile %v, link %v", timeCompilation, timeLinking)
251 |
252 | // Run the binary if it's requested
253 | if hasCommand("run") && viper.GetString("type") == "exe" {
254 | // We have to use the absolute path here to make this work on Linux and MacOS
255 | absOutPath, _ := filepath.Abs(outPath)
256 | cmd := exec.Command(absOutPath)
257 | cmd.Args = slices.Concat([]string{absOutPath}, pflag.Args()[1:])
258 | cmd.Stdin = os.Stdin
259 | cmd.Stdout = os.Stdout
260 | cmd.Stderr = os.Stderr
261 | cmd.Run()
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/package.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os/exec"
5 | "runtime"
6 | "strings"
7 |
8 | "github.com/mattn/go-shellwords"
9 | "github.com/spf13/viper"
10 | )
11 |
12 | // Package contains basic information about a library.
13 | type Package struct {
14 | Name string
15 | }
16 |
17 | func addPackage(options *CompilerOptions, name string) *Package {
18 | if ret := addPackageLocal(options, name); ret != nil {
19 | return ret
20 | }
21 |
22 | //TODO: Implement global packages
23 |
24 | if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
25 | if ret := addPackagePkgconfig(options, name); ret != nil {
26 | return ret
27 | }
28 | }
29 |
30 | //TODO: Implement vcpkg
31 | //if runtime.GOOS == "windows" {
32 | //}
33 |
34 | return nil
35 | }
36 |
37 | func addPackageLocal(options *CompilerOptions, name string) *Package {
38 | packageInfo := viper.GetStringMap("package." + name)
39 | if len(packageInfo) == 0 {
40 | return nil
41 | }
42 |
43 | return configurePackageFromConfig(options, packageInfo, name)
44 | }
45 |
46 | func addPackagePkgconfig(options *CompilerOptions, name string) *Package {
47 | // pkg-config must be installed for this to work
48 | _, err := exec.LookPath("pkg-config")
49 | if err != nil {
50 | return nil
51 | }
52 |
53 | cmdCflags := exec.Command("pkg-config", name, "--cflags")
54 | outputCflags, err := cmdCflags.CombinedOutput()
55 | if err != nil {
56 | return nil
57 | }
58 |
59 | cmdLibs := exec.Command("pkg-config", name, "--libs")
60 | outputLibs, err := cmdLibs.CombinedOutput()
61 | if err != nil {
62 | return nil
63 | }
64 |
65 | parseCflags, _ := shellwords.Parse(strings.Trim(string(outputCflags), "\r\n"))
66 | parseLibs, _ := shellwords.Parse(strings.Trim(string(outputLibs), "\r\n"))
67 |
68 | options.CompilerFlagsCXX = append(options.CompilerFlagsCXX, parseCflags...)
69 | options.LinkerFlags = append(options.LinkerFlags, parseLibs...)
70 |
71 | return &Package{
72 | Name: name,
73 | }
74 | }
75 |
76 | func configurePackageFromConfig(options *CompilerOptions, pkg map[string]interface{}, name string) *Package {
77 | maybeUnpack(&options.IncludeDirectories, pkg["includes"])
78 | maybeUnpack(&options.LinkDirectories, pkg["linkdirs"])
79 | maybeUnpack(&options.LinkLibraries, pkg["links"])
80 | maybeUnpack(&options.Defines, pkg["defines"])
81 | maybeUnpack(&options.CompilerFlagsCXX, pkg["cflags"])
82 | maybeUnpack(&options.LinkerFlags, pkg["lflags"])
83 |
84 | return &Package{
85 | Name: name,
86 | }
87 | }
88 |
89 | func maybeUnpack(dest *[]string, src interface{}) {
90 | if src == nil {
91 | return
92 | }
93 |
94 | for _, val := range src.([]interface{}) {
95 | (*dest) = append(*dest, val.(string))
96 | }
97 | }
98 |
--------------------------------------------------------------------------------