Skip to content

Export CMake project if compiling for a suitable core #234

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ func (s *Builder) Run(ctx *types.Context) error {

&PrintUsedLibrariesIfVerbose{},

&ExportProjectCMake{SketchError: mainErr != nil},

&phases.Sizer{SketchError: mainErr != nil},
}
otherErr := runCommands(ctx, commands, false)
Expand Down
3 changes: 2 additions & 1 deletion constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@ const BUILD_PROPERTIES_BUILD_SYSTEM_PATH = "build.system.path"
const BUILD_PROPERTIES_BUILD_VARIANT = "build.variant"
const BUILD_PROPERTIES_BUILD_VARIANT_PATH = "build.variant.path"
const BUILD_PROPERTIES_COMPILER_C_ELF_FLAGS = "compiler.c.elf.flags"
const BUILD_PROPERTIES_COMPILER_C_ELF_EXTRAFLAGS = "compiler.c.elf.extra_flags"
const BUILD_PROPERTIES_COMPILER_LDFLAGS = "compiler.ldflags"
const BUILD_PROPERTIES_COMPILER_CPP_FLAGS = "compiler.cpp.flags"
const BUILD_PROPERTIES_COMPILER_PATH = "compiler.path"
const BUILD_PROPERTIES_COMPILER_WARNING_FLAGS = "compiler.warning_flags"
const BUILD_PROPERTIES_COMPILER_EXPORT_CMAKE_FLAGS = "compiler.export_cmake"
const BUILD_PROPERTIES_EXTRA_TIME_DST = "extra.time.dst"
const BUILD_PROPERTIES_EXTRA_TIME_LOCAL = "extra.time.local"
const BUILD_PROPERTIES_EXTRA_TIME_UTC = "extra.time.utc"
Expand Down
227 changes: 227 additions & 0 deletions create_cmake_rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/*
* This file is part of Arduino Builder.
*
* Arduino Builder is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* As a special exception, you may use this file as part of a free software
* library without restriction. Specifically, if other files instantiate
* templates or use macros or inline functions from this file, or you compile
* this file and link it with other files to produce an executable, this
* file does not by itself cause the resulting executable to be covered by
* the GNU General Public License. This exception does not however
* invalidate any other reasons why the executable file might be covered by
* the GNU General Public License.
*
* Copyright 2015 Arduino LLC (http://www.arduino.cc/)
*/

package builder

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/arduino/arduino-builder/builder_utils"
"github.com/arduino/arduino-builder/constants"
"github.com/arduino/arduino-builder/i18n"
"github.com/arduino/arduino-builder/types"
"github.com/arduino/arduino-builder/utils"
)

var VALID_EXPORT_EXTENSIONS = map[string]bool{".h": true, ".c": true, ".hpp": true, ".hh": true, ".cpp": true, ".s": true, ".a": true}
var DOTHEXTENSION = map[string]bool{".h": true, ".hh": true, ".hpp": true}
var DOTAEXTENSION = map[string]bool{".a": true}

type ExportProjectCMake struct {
// Was there an error while compiling the sketch?
SketchError bool
}

func (s *ExportProjectCMake) Run(ctx *types.Context) error {
//verbose := ctx.Verbose
logger := ctx.GetLogger()

if s.SketchError || !canExportCmakeProject(ctx) {
return nil
}

// Create new cmake subFolder - clean if the folder is already there
cmakeFolder := filepath.Join(ctx.BuildPath, "_cmake")
if _, err := os.Stat(cmakeFolder); err == nil {
os.RemoveAll(cmakeFolder)
}
os.Mkdir(cmakeFolder, 0777)

// Create lib and build subfolders
libBaseFolder := filepath.Join(cmakeFolder, "lib")
os.Mkdir(libBaseFolder, 0777)
buildBaseFolder := filepath.Join(cmakeFolder, "build")
os.Mkdir(buildBaseFolder, 0777)

// Create core subfolder path (don't create it yet)
coreFolder := filepath.Join(cmakeFolder, "core")
cmakeFile := filepath.Join(cmakeFolder, "CMakeLists.txt")

// Copy used libraries in the correct folder
extensions := func(ext string) bool { return VALID_EXPORT_EXTENSIONS[ext] }
for _, library := range ctx.ImportedLibraries {
libFolder := filepath.Join(libBaseFolder, library.Name)
utils.CopyDir(library.Folder, libFolder, extensions)
// Remove examples folder
if _, err := os.Stat(filepath.Join(libFolder, "examples")); err == nil {
os.RemoveAll(filepath.Join(libFolder, "examples"))
}
}

// Copy core + variant in use + preprocessed sketch in the correct folders
err := utils.CopyDir(ctx.BuildProperties[constants.BUILD_PROPERTIES_BUILD_CORE_PATH], coreFolder, extensions)
if err != nil {
fmt.Println(err)
}
err = utils.CopyDir(ctx.BuildProperties[constants.BUILD_PROPERTIES_BUILD_VARIANT_PATH], filepath.Join(coreFolder, "variant"), extensions)
if err != nil {
fmt.Println(err)
}
err = utils.CopyDir(ctx.SketchBuildPath, filepath.Join(cmakeFolder, "sketch"), extensions)
if err != nil {
fmt.Println(err)
}

// Extract CFLAGS, CPPFLAGS and LDFLAGS
var defines []string
var linkerflags []string
var libs []string
var linkDirectories []string

extractCompileFlags(ctx, constants.RECIPE_C_COMBINE_PATTERN, &defines, &libs, &linkerflags, &linkDirectories, logger)
extractCompileFlags(ctx, constants.RECIPE_C_PATTERN, &defines, &libs, &linkerflags, &linkDirectories, logger)
extractCompileFlags(ctx, constants.RECIPE_CPP_PATTERN, &defines, &libs, &linkerflags, &linkDirectories, logger)

// Extract folders with .h in them for adding in include list
var headerFiles []string
isHeader := func(ext string) bool { return DOTHEXTENSION[ext] }
utils.FindFilesInFolder(&headerFiles, cmakeFolder, isHeader, true)
foldersContainingDotH := findUniqueFoldersRelative(headerFiles, cmakeFolder)

// Extract folders with .a in them for adding in static libs paths list
var staticLibsFiles []string
isStaticLib := func(ext string) bool { return DOTAEXTENSION[ext] }
utils.FindFilesInFolder(&staticLibsFiles, cmakeFolder, isStaticLib, true)

// Generate the CMakeLists global file

projectName := strings.TrimSuffix(filepath.Base(ctx.Sketch.MainFile.Name), filepath.Ext(ctx.Sketch.MainFile.Name))

cmakelist := "cmake_minimum_required(VERSION 3.5.0)\n"
cmakelist += "INCLUDE(FindPkgConfig)\n"
cmakelist += "project (" + projectName + " C CXX)\n"
cmakelist += "add_definitions (" + strings.Join(defines, " ") + " " + strings.Join(linkerflags, " ") + ")\n"
cmakelist += "include_directories (" + foldersContainingDotH + ")\n"

// Make link directories relative
// We can totally discard them since they mostly are outside the core folder
// If they are inside the core they are not getting copied :)
var relLinkDirectories []string
for _, dir := range linkDirectories {
if strings.Contains(dir, cmakeFolder) {
relLinkDirectories = append(relLinkDirectories, strings.TrimPrefix(dir, cmakeFolder))
}
}

// Add SO_PATHS option for libraries not getting found by pkg_config
cmakelist += "set(EXTRA_LIBS_DIRS \"\" CACHE STRING \"Additional paths for dynamic libraries\")\n"

for i, lib := range libs {
// Dynamic libraries should be discovered by pkg_config
lib = strings.TrimPrefix(lib, "-l")
libs[i] = lib
cmakelist += "pkg_search_module (" + strings.ToUpper(lib) + " " + lib + ")\n"
relLinkDirectories = append(relLinkDirectories, "${"+strings.ToUpper(lib)+"_LIBRARY_DIRS}")
}
cmakelist += "link_directories (" + strings.Join(relLinkDirectories, " ") + " ${EXTRA_LIBS_DIRS})\n"
for _, staticLibsFile := range staticLibsFiles {
// Static libraries are fully configured
lib := filepath.Base(staticLibsFile)
lib = strings.TrimPrefix(lib, "lib")
lib = strings.TrimSuffix(lib, ".a")
if !utils.SliceContains(libs, lib) {
libs = append(libs, lib)
cmakelist += "add_library (" + lib + " STATIC IMPORTED)\n"
location := strings.TrimPrefix(staticLibsFile, cmakeFolder)
cmakelist += "set_property(TARGET " + lib + " PROPERTY IMPORTED_LOCATION " + "${PROJECT_SOURCE_DIR}" + location + " )\n"
}
}
// Include source files
// TODO: remove .cpp and .h from libraries example folders
cmakelist += "file (GLOB_RECURSE SOURCES core/*.c* lib/*.c* sketch/*.c*)\n"

// Compile and link project
cmakelist += "add_executable (" + projectName + " ${SOURCES} ${SOURCES_LIBS})\n"
cmakelist += "target_link_libraries( " + projectName + " " + strings.Join(libs, " ") + ")\n"

utils.WriteFile(cmakeFile, cmakelist)

return nil
}

func canExportCmakeProject(ctx *types.Context) bool {
return ctx.BuildProperties[constants.BUILD_PROPERTIES_COMPILER_EXPORT_CMAKE_FLAGS] != ""
}

func extractCompileFlags(ctx *types.Context, receipe string, defines, libs, linkerflags, linkDirectories *[]string, logger i18n.Logger) {
command, _ := builder_utils.PrepareCommandForRecipe(ctx.BuildProperties, receipe, true, false, false, logger)

for _, arg := range command.Args {
if strings.HasPrefix(arg, "-D") {
*defines = appendIfUnique(*defines, arg)
continue
}
if strings.HasPrefix(arg, "-l") {
*libs = appendIfUnique(*libs, arg)
continue
}
if strings.HasPrefix(arg, "-L") {
*linkDirectories = appendIfUnique(*linkDirectories, strings.TrimPrefix(arg, "-L"))
continue
}
if strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "-I") {
// HACK : from linkerflags remove MMD (no cache is produced)
if !strings.HasPrefix(arg, "-MMD") {
*linkerflags = appendIfUnique(*linkerflags, arg)
}
}
}
}

func findUniqueFoldersRelative(slice []string, base string) string {
var out []string
for _, element := range slice {
path := filepath.Dir(element)
path = strings.TrimPrefix(path, base+"/")
if !utils.SliceContains(out, path) {
out = append(out, path)
}
}
return strings.Join(out, " ")
}

func appendIfUnique(slice []string, element string) []string {
if !utils.SliceContains(slice, element) {
slice = append(slice, element)
}
return slice
}
2 changes: 1 addition & 1 deletion phases/libraries_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func fixLDFLAGforPrecompiledLibraries(ctx *types.Context, libraries []*types.Lib
name = strings.Replace(name, "lib", "", 1)
libs_cmd += "-l" + name + " "
}
ctx.BuildProperties[constants.BUILD_PROPERTIES_COMPILER_C_ELF_EXTRAFLAGS] += "\"-L" + path + "\" " + libs_cmd
ctx.BuildProperties[constants.BUILD_PROPERTIES_COMPILER_LDFLAGS] += "\"-L" + path + "\" " + libs_cmd
}
}
return nil
Expand Down
108 changes: 108 additions & 0 deletions utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ package utils
import (
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
Expand Down Expand Up @@ -488,3 +490,109 @@ func ParseCppString(line string) (string, string, bool) {
i += width
}
}

// CopyFile copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all it's contents will be replaced by the contents
// of the source file. The file mode will be copied from the source and
// the copied data is synced/flushed to stable storage.
func CopyFile(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}
defer in.Close()

out, err := os.Create(dst)
if err != nil {
return
}
defer func() {
if e := out.Close(); e != nil {
err = e
}
}()

_, err = io.Copy(out, in)
if err != nil {
return
}

err = out.Sync()
if err != nil {
return
}

si, err := os.Stat(src)
if err != nil {
return
}
err = os.Chmod(dst, si.Mode())
if err != nil {
return
}

return
}

// CopyDir recursively copies a directory tree, attempting to preserve permissions.
// Source directory must exist, destination directory must *not* exist.
// Symlinks are ignored and skipped.
func CopyDir(src string, dst string, extensions CheckExtensionFunc) (err error) {
src = filepath.Clean(src)
dst = filepath.Clean(dst)

si, err := os.Stat(src)
if err != nil {
return err
}
if !si.IsDir() {
return fmt.Errorf("source is not a directory")
}

_, err = os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
return
}
if err == nil {
return fmt.Errorf("destination already exists")
}

err = os.MkdirAll(dst, si.Mode())
if err != nil {
return
}

entries, err := ioutil.ReadDir(src)
if err != nil {
return
}

for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())

if entry.IsDir() {
err = CopyDir(srcPath, dstPath, extensions)
if err != nil {
return
}
} else {
// Skip symlinks.
if entry.Mode()&os.ModeSymlink != 0 {
continue
}

if extensions != nil && !extensions(strings.ToLower(filepath.Ext(srcPath))) {
continue
}

err = CopyFile(srcPath, dstPath)
if err != nil {
return
}
}
}

return
}