Skip to content

Commit 3803fa3

Browse files
facchinmcmaglie
authored andcommitted
Export CMake project if the build is successfull
The project contains: - all the needed files (core, precompiled and source libraries, preprocessed sketch) - a CMakeList.txt stub which should just work (not targeting any architecture except linux-x86_64 ATM) Next steps: produce a zip file and restrict the execution to suitable cores
1 parent edfe966 commit 3803fa3

File tree

5 files changed

+335
-2
lines changed

5 files changed

+335
-2
lines changed

builder.go

+2
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ func (s *Builder) Run(ctx *types.Context) error {
128128

129129
&PrintUsedLibrariesIfVerbose{},
130130

131+
&ExportProjectCMake{SketchError: mainErr != nil},
132+
131133
&phases.Sizer{SketchError: mainErr != nil},
132134
}
133135
otherErr := runCommands(ctx, commands, false)

constants/constants.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const BUILD_PROPERTIES_BUILD_SYSTEM_PATH = "build.system.path"
4848
const BUILD_PROPERTIES_BUILD_VARIANT = "build.variant"
4949
const BUILD_PROPERTIES_BUILD_VARIANT_PATH = "build.variant.path"
5050
const BUILD_PROPERTIES_COMPILER_C_ELF_FLAGS = "compiler.c.elf.flags"
51-
const BUILD_PROPERTIES_COMPILER_C_ELF_EXTRAFLAGS = "compiler.c.elf.extra_flags"
51+
const BUILD_PROPERTIES_COMPILER_LDFLAGS = "compiler.ldflags"
5252
const BUILD_PROPERTIES_COMPILER_CPP_FLAGS = "compiler.cpp.flags"
5353
const BUILD_PROPERTIES_COMPILER_PATH = "compiler.path"
5454
const BUILD_PROPERTIES_COMPILER_WARNING_FLAGS = "compiler.warning_flags"

phases/libraries_builder.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func fixLDFLAGforPrecompiledLibraries(ctx *types.Context, libraries []*types.Lib
9393
name = strings.Replace(name, "lib", "", 1)
9494
libs_cmd += "-l" + name + " "
9595
}
96-
ctx.BuildProperties[constants.BUILD_PROPERTIES_COMPILER_C_ELF_EXTRAFLAGS] += "\"-L" + path + "\" " + libs_cmd
96+
ctx.BuildProperties[constants.BUILD_PROPERTIES_COMPILER_LDFLAGS] += "\"-L" + path + "\" " + libs_cmd
9797
}
9898
}
9999
return nil
+223
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/*
2+
* This file is part of Arduino Builder.
3+
*
4+
* Arduino Builder is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17+
*
18+
* As a special exception, you may use this file as part of a free software
19+
* library without restriction. Specifically, if other files instantiate
20+
* templates or use macros or inline functions from this file, or you compile
21+
* this file and link it with other files to produce an executable, this
22+
* file does not by itself cause the resulting executable to be covered by
23+
* the GNU General Public License. This exception does not however
24+
* invalidate any other reasons why the executable file might be covered by
25+
* the GNU General Public License.
26+
*
27+
* Copyright 2015 Arduino LLC (http://www.arduino.cc/)
28+
*/
29+
30+
package builder
31+
32+
import (
33+
"fmt"
34+
"os"
35+
"path/filepath"
36+
"strings"
37+
38+
"arduino.cc/builder/builder_utils"
39+
"arduino.cc/builder/constants"
40+
"arduino.cc/builder/i18n"
41+
"arduino.cc/builder/types"
42+
"arduino.cc/builder/utils"
43+
)
44+
45+
var VALID_EXPORT_EXTENSIONS = map[string]bool{".h": true, ".c": true, ".hpp": true, ".hh": true, ".cpp": true, ".s": true, ".a": true}
46+
var DOTHEXTENSION = map[string]bool{".h": true, ".hh": true, ".hpp": true}
47+
var DOTAEXTENSION = map[string]bool{".a": true}
48+
49+
type ExportProjectCMake struct {
50+
// Was there an error while compiling the sketch?
51+
SketchError bool
52+
}
53+
54+
func (s *ExportProjectCMake) Run(ctx *types.Context) error {
55+
//verbose := ctx.Verbose
56+
logger := ctx.GetLogger()
57+
58+
if s.SketchError {
59+
return nil
60+
}
61+
62+
// Create new cmake subFolder - clean if the folder is already there
63+
cmakeFolder := filepath.Join(ctx.BuildPath, "_cmake")
64+
if _, err := os.Stat(cmakeFolder); err == nil {
65+
os.RemoveAll(cmakeFolder)
66+
}
67+
os.Mkdir(cmakeFolder, 0777)
68+
69+
// Create lib and build subfolders
70+
libBaseFolder := filepath.Join(cmakeFolder, "lib")
71+
os.Mkdir(libBaseFolder, 0777)
72+
buildBaseFolder := filepath.Join(cmakeFolder, "build")
73+
os.Mkdir(buildBaseFolder, 0777)
74+
75+
// Create core subfolder path (don't create it yet)
76+
coreFolder := filepath.Join(cmakeFolder, "core")
77+
cmakeFile := filepath.Join(cmakeFolder, "CMakeLists.txt")
78+
79+
// Copy used libraries in the correct folder
80+
extensions := func(ext string) bool { return VALID_EXPORT_EXTENSIONS[ext] }
81+
for _, library := range ctx.ImportedLibraries {
82+
libFolder := filepath.Join(libBaseFolder, library.Name)
83+
utils.CopyDir(library.Folder, libFolder, extensions)
84+
// Remove examples folder
85+
if _, err := os.Stat(filepath.Join(libFolder, "examples")); err == nil {
86+
os.RemoveAll(filepath.Join(libFolder, "examples"))
87+
}
88+
}
89+
90+
// Copy core + variant in use + preprocessed sketch in the correct folders
91+
err := utils.CopyDir(ctx.BuildProperties[constants.BUILD_PROPERTIES_BUILD_CORE_PATH], coreFolder, extensions)
92+
if err != nil {
93+
fmt.Println(err)
94+
}
95+
err = utils.CopyDir(ctx.BuildProperties[constants.BUILD_PROPERTIES_BUILD_VARIANT_PATH], filepath.Join(coreFolder, "variant"), extensions)
96+
if err != nil {
97+
fmt.Println(err)
98+
}
99+
err = utils.CopyDir(ctx.SketchBuildPath, filepath.Join(cmakeFolder, "sketch"), extensions)
100+
if err != nil {
101+
fmt.Println(err)
102+
}
103+
104+
// Extract CFLAGS, CPPFLAGS and LDFLAGS
105+
var defines []string
106+
var linkerflags []string
107+
var libs []string
108+
var linkDirectories []string
109+
110+
extractCompileFlags(ctx, constants.RECIPE_C_COMBINE_PATTERN, &defines, &libs, &linkerflags, &linkDirectories, logger)
111+
extractCompileFlags(ctx, constants.RECIPE_C_PATTERN, &defines, &libs, &linkerflags, &linkDirectories, logger)
112+
extractCompileFlags(ctx, constants.RECIPE_CPP_PATTERN, &defines, &libs, &linkerflags, &linkDirectories, logger)
113+
114+
// Extract folders with .h in them for adding in include list
115+
var headerFiles []string
116+
isHeader := func(ext string) bool { return DOTHEXTENSION[ext] }
117+
utils.FindFilesInFolder(&headerFiles, cmakeFolder, isHeader, true)
118+
foldersContainingDotH := findUniqueFoldersRelative(headerFiles, cmakeFolder)
119+
120+
// Extract folders with .a in them for adding in static libs paths list
121+
var staticLibsFiles []string
122+
isStaticLib := func(ext string) bool { return DOTAEXTENSION[ext] }
123+
utils.FindFilesInFolder(&staticLibsFiles, cmakeFolder, isStaticLib, true)
124+
125+
// Generate the CMakeLists global file
126+
127+
projectName := strings.TrimSuffix(filepath.Base(ctx.Sketch.MainFile.Name), filepath.Ext(ctx.Sketch.MainFile.Name))
128+
129+
cmakelist := "cmake_minimum_required(VERSION 3.5.0)\n"
130+
cmakelist += "INCLUDE(FindPkgConfig)\n"
131+
cmakelist += "project (" + projectName + " C CXX)\n"
132+
cmakelist += "add_definitions (" + strings.Join(defines, " ") + " " + strings.Join(linkerflags, " ") + ")\n"
133+
cmakelist += "include_directories (" + foldersContainingDotH + ")\n"
134+
135+
// Make link directories relative
136+
// We can totally discard them since they mostly are outside the core folder
137+
// If they are inside the core they are not getting copied :)
138+
var relLinkDirectories []string
139+
for _, dir := range linkDirectories {
140+
if strings.Contains(dir, cmakeFolder) {
141+
relLinkDirectories = append(relLinkDirectories, strings.TrimPrefix(dir, cmakeFolder))
142+
}
143+
}
144+
145+
// Add SO_PATHS option for libraries not getting found by pkg_config
146+
cmakelist += "set(EXTRA_LIBS_DIRS \"\" CACHE STRING \"Additional paths for dynamic libraries\")\n"
147+
148+
for i, lib := range libs {
149+
// Dynamic libraries should be discovered by pkg_config
150+
lib = strings.TrimPrefix(lib, "-l")
151+
libs[i] = lib
152+
cmakelist += "pkg_search_module (" + strings.ToUpper(lib) + " " + lib + ")\n"
153+
relLinkDirectories = append(relLinkDirectories, "${"+strings.ToUpper(lib)+"_LIBRARY_DIRS}")
154+
}
155+
cmakelist += "link_directories (" + strings.Join(relLinkDirectories, " ") + " ${EXTRA_LIBS_DIRS})\n"
156+
for _, staticLibsFile := range staticLibsFiles {
157+
// Static libraries are fully configured
158+
lib := filepath.Base(staticLibsFile)
159+
lib = strings.TrimPrefix(lib, "lib")
160+
lib = strings.TrimSuffix(lib, ".a")
161+
if !utils.SliceContains(libs, lib) {
162+
libs = append(libs, lib)
163+
cmakelist += "add_library (" + lib + " STATIC IMPORTED)\n"
164+
location := strings.TrimPrefix(staticLibsFile, cmakeFolder)
165+
cmakelist += "set_property(TARGET " + lib + " PROPERTY IMPORTED_LOCATION " + "${PROJECT_SOURCE_DIR}" + location + " )\n"
166+
}
167+
}
168+
// Include source files
169+
// TODO: remove .cpp and .h from libraries example folders
170+
cmakelist += "file (GLOB_RECURSE SOURCES core/*.c* lib/*.c* sketch/*.c*)\n"
171+
172+
// Compile and link project
173+
cmakelist += "add_executable (" + projectName + " ${SOURCES} ${SOURCES_LIBS})\n"
174+
cmakelist += "target_link_libraries( " + projectName + " " + strings.Join(libs, " ") + ")\n"
175+
176+
utils.WriteFile(cmakeFile, cmakelist)
177+
178+
return nil
179+
}
180+
181+
func extractCompileFlags(ctx *types.Context, receipe string, defines, libs, linkerflags, linkDirectories *[]string, logger i18n.Logger) {
182+
command, _ := builder_utils.PrepareCommandForRecipe(ctx.BuildProperties, receipe, true, false, false, logger)
183+
184+
for _, arg := range command.Args {
185+
if strings.HasPrefix(arg, "-D") {
186+
*defines = appendIfUnique(*defines, arg)
187+
continue
188+
}
189+
if strings.HasPrefix(arg, "-l") {
190+
*libs = appendIfUnique(*libs, arg)
191+
continue
192+
}
193+
if strings.HasPrefix(arg, "-L") {
194+
*linkDirectories = appendIfUnique(*linkDirectories, strings.TrimPrefix(arg, "-L"))
195+
continue
196+
}
197+
if strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "-I") {
198+
// HACK : from linkerflags remove MMD (no cache is produced)
199+
if !strings.HasPrefix(arg, "-MMD") {
200+
*linkerflags = appendIfUnique(*linkerflags, arg)
201+
}
202+
}
203+
}
204+
}
205+
206+
func findUniqueFoldersRelative(slice []string, base string) string {
207+
var out []string
208+
for _, element := range slice {
209+
path := filepath.Dir(element)
210+
path = strings.TrimPrefix(path, base+"/")
211+
if !utils.SliceContains(out, path) {
212+
out = append(out, path)
213+
}
214+
}
215+
return strings.Join(out, " ")
216+
}
217+
218+
func appendIfUnique(slice []string, element string) []string {
219+
if !utils.SliceContains(slice, element) {
220+
slice = append(slice, element)
221+
}
222+
return slice
223+
}

utils/utils.go

+108
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ package utils
3232
import (
3333
"crypto/md5"
3434
"encoding/hex"
35+
"fmt"
36+
"io"
3537
"io/ioutil"
3638
"os"
3739
"os/exec"
@@ -488,3 +490,109 @@ func ParseCppString(line string) (string, string, bool) {
488490
i += width
489491
}
490492
}
493+
494+
// CopyFile copies the contents of the file named src to the file named
495+
// by dst. The file will be created if it does not already exist. If the
496+
// destination file exists, all it's contents will be replaced by the contents
497+
// of the source file. The file mode will be copied from the source and
498+
// the copied data is synced/flushed to stable storage.
499+
func CopyFile(src, dst string) (err error) {
500+
in, err := os.Open(src)
501+
if err != nil {
502+
return
503+
}
504+
defer in.Close()
505+
506+
out, err := os.Create(dst)
507+
if err != nil {
508+
return
509+
}
510+
defer func() {
511+
if e := out.Close(); e != nil {
512+
err = e
513+
}
514+
}()
515+
516+
_, err = io.Copy(out, in)
517+
if err != nil {
518+
return
519+
}
520+
521+
err = out.Sync()
522+
if err != nil {
523+
return
524+
}
525+
526+
si, err := os.Stat(src)
527+
if err != nil {
528+
return
529+
}
530+
err = os.Chmod(dst, si.Mode())
531+
if err != nil {
532+
return
533+
}
534+
535+
return
536+
}
537+
538+
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
539+
// Source directory must exist, destination directory must *not* exist.
540+
// Symlinks are ignored and skipped.
541+
func CopyDir(src string, dst string, extensions CheckExtensionFunc) (err error) {
542+
src = filepath.Clean(src)
543+
dst = filepath.Clean(dst)
544+
545+
si, err := os.Stat(src)
546+
if err != nil {
547+
return err
548+
}
549+
if !si.IsDir() {
550+
return fmt.Errorf("source is not a directory")
551+
}
552+
553+
_, err = os.Stat(dst)
554+
if err != nil && !os.IsNotExist(err) {
555+
return
556+
}
557+
if err == nil {
558+
return fmt.Errorf("destination already exists")
559+
}
560+
561+
err = os.MkdirAll(dst, si.Mode())
562+
if err != nil {
563+
return
564+
}
565+
566+
entries, err := ioutil.ReadDir(src)
567+
if err != nil {
568+
return
569+
}
570+
571+
for _, entry := range entries {
572+
srcPath := filepath.Join(src, entry.Name())
573+
dstPath := filepath.Join(dst, entry.Name())
574+
575+
if entry.IsDir() {
576+
err = CopyDir(srcPath, dstPath, extensions)
577+
if err != nil {
578+
return
579+
}
580+
} else {
581+
// Skip symlinks.
582+
if entry.Mode()&os.ModeSymlink != 0 {
583+
continue
584+
}
585+
586+
if extensions != nil && !extensions(strings.ToLower(filepath.Ext(srcPath))) {
587+
continue
588+
}
589+
590+
err = CopyFile(srcPath, dstPath)
591+
if err != nil {
592+
return
593+
}
594+
}
595+
}
596+
597+
return
598+
}

0 commit comments

Comments
 (0)