| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package builder |
| |
| import ( |
| "bufio" |
| "bytes" |
| "fmt" |
| "io" |
| "os" |
| "path" |
| "path/filepath" |
| "strings" |
| |
| log "github.com/sirupsen/logrus" |
| |
| "mynewt.apache.org/newt/newt/interfaces" |
| "mynewt.apache.org/newt/newt/project" |
| "mynewt.apache.org/newt/newt/target" |
| "mynewt.apache.org/newt/newt/toolchain" |
| "mynewt.apache.org/newt/util" |
| ) |
| |
| const CMAKELISTS_FILENAME string = "CMakeLists.txt" |
| |
| func CmakeListsPath() string { |
| return project.GetProject().BasePath + "/" + CMAKELISTS_FILENAME |
| } |
| |
| func EscapeName(name string) string { |
| return strings.Replace(name, "/", "_", -1) |
| } |
| |
| func ExtractLibraryName(filepath string) string { |
| _, name := path.Split(filepath) |
| name = strings.TrimPrefix(name, "lib") |
| name = strings.TrimSuffix(name, ".a") |
| return name |
| } |
| |
| func replaceBackslashes(path string) string { |
| return strings.Replace(path, "\\", "/", -1) |
| } |
| |
| func replaceBackslashesSlice(elements []string) { |
| for e := range elements { |
| elements[e] = replaceBackslashes(elements[e]) |
| } |
| } |
| |
| func escapeFlags(flag string) string { |
| flag = strings.Replace(flag, "\"", "\\\\\\\"", -1) |
| flag = strings.Replace(flag, "(", "\\\\(", -1) |
| flag = strings.Replace(flag, ")", "\\\\)", -1) |
| flag = strings.Replace(flag, "<", "\\\\<", -1) |
| flag = strings.Replace(flag, ">", "\\\\>", -1) |
| return flag |
| } |
| |
| func escapeFlagsSlice(flags []string) []string { |
| for f := range flags { |
| flags[f] = escapeFlags(flags[f]) |
| } |
| |
| return flags |
| } |
| |
| func trimProjectPath(path string) string { |
| proj := interfaces.GetProject() |
| path = strings.TrimPrefix(path, proj.Path()+"/") |
| return path |
| } |
| |
| func trimProjectPathSlice(elements []string) { |
| for e := range elements { |
| elements[e] = trimProjectPath(elements[e]) |
| } |
| } |
| |
| func extractIncludes(flags *[]string, includes *[]string, other *[]string) { |
| for _, f := range *flags { |
| if strings.HasPrefix(f, "-I") { |
| *includes = append(*includes, strings.TrimPrefix(f, "-I")) |
| } else { |
| *other = append(*other, f) |
| } |
| } |
| } |
| |
| func CmakeSourceObjectWrite(w io.Writer, cj toolchain.CompilerJob, |
| includeDirs *[]string, linkFlags *[]string) { |
| c := cj.Compiler |
| |
| flags := []string{} |
| otherFlags := []string{} |
| |
| switch cj.CompilerType { |
| case toolchain.COMPILER_TYPE_C: |
| flags = append(flags, c.GetCompilerInfo().Cflags...) |
| flags = append(flags, c.GetLocalCompilerInfo().Cflags...) |
| case toolchain.COMPILER_TYPE_ASM: |
| flags = append(flags, c.GetCompilerInfo().Cflags...) |
| flags = append(flags, c.GetLocalCompilerInfo().Cflags...) |
| flags = append(flags, c.GetCompilerInfo().Aflags...) |
| flags = append(flags, c.GetLocalCompilerInfo().Aflags...) |
| case toolchain.COMPILER_TYPE_CPP: |
| flags = append(flags, c.GetCompilerInfo().Cflags...) |
| flags = append(flags, c.GetLocalCompilerInfo().Cflags...) |
| flags = append(flags, c.GetCompilerInfo().CXXflags...) |
| flags = append(flags, c.GetLocalCompilerInfo().CXXflags...) |
| } |
| |
| *linkFlags = append(*linkFlags, c.GetCompilerInfo().Lflags...) |
| *linkFlags = append(*linkFlags, c.GetLocalCompilerInfo().Lflags...) |
| |
| extractIncludes(&flags, includeDirs, &otherFlags) |
| cj.Filename = trimProjectPath(cj.Filename) |
| |
| // Sort and remove duplicate flags |
| otherFlags = util.SortFields(otherFlags...) |
| |
| fmt.Fprintf(w, |
| `set_property(SOURCE %s APPEND_STRING |
| PROPERTY COMPILE_FLAGS |
| "%s")`, |
| cj.Filename, |
| strings.Join(escapeFlagsSlice(otherFlags), " ")) |
| fmt.Fprintln(w) |
| } |
| |
| func (b *Builder) CMakeBuildPackageWrite(w io.Writer, bpkg *BuildPackage, |
| linkFlags *[]string, libraries *[]string) (*BuildPackage, error) { |
| entries, err := b.collectCompileEntriesBpkg(bpkg) |
| if err != nil { |
| return nil, err |
| } |
| |
| if len(entries) <= 0 { |
| return nil, nil |
| } |
| |
| otherIncludes := []string{} |
| files := []string{} |
| linkDirs := []string{} |
| |
| for _, s := range entries { |
| filename := filepath.ToSlash(s.Filename) |
| if s.Compiler.ShouldIgnoreFile(filename) { |
| log.Infof("Ignoring %s because package dictates it.\n", filename) |
| continue |
| } |
| |
| if s.CompilerType == toolchain.COMPILER_TYPE_ARCHIVE { |
| arFile := trimProjectPath(s.Filename) |
| linkDirs = append(linkDirs, filepath.Dir(arFile)) |
| *libraries = append(*libraries, arFile) |
| } else { |
| CmakeSourceObjectWrite(w, s, &otherIncludes, linkFlags) |
| s.Filename = trimProjectPath(s.Filename) |
| files = append(files, s.Filename) |
| } |
| } |
| |
| if len(linkDirs) > 0 { |
| replaceBackslashesSlice(linkDirs) |
| fmt.Fprintf(w, "link_directories(%s)\n", strings.Join(util.SortFields(linkDirs...), " ")) |
| } |
| |
| if len(files) <= 0 { |
| return nil, nil |
| } |
| |
| pkgName := bpkg.rpkg.Lpkg.Name() |
| |
| util.StatusMessage(util.VERBOSITY_DEFAULT, "Generating CMakeLists.txt for %s\n", pkgName) |
| fmt.Fprintf(w, "\n# Generating CMakeLists.txt for %s\n", pkgName) |
| fmt.Fprintf(w, "add_library(%s %s)\n", |
| EscapeName(pkgName), |
| strings.Join(files, " ")) |
| archivePath := replaceBackslashes(trimProjectPath(filepath.Dir(b.ArchivePath(bpkg)))) |
| CmakeCompilerInfoWrite(w, archivePath, bpkg, entries[0], otherIncludes) |
| |
| return bpkg, nil |
| } |
| |
| func (b *Builder) CMakeTargetWrite(w io.Writer, targetCompiler *toolchain.Compiler) error { |
| bpkgs := b.sortedBuildPackages() |
| var compileFlags []string |
| var linkFlags []string |
| var libraries []string |
| |
| c := targetCompiler |
| c.AddInfo(b.GetCompilerInfo()) |
| |
| builtPackages := []*BuildPackage{} |
| for _, bpkg := range bpkgs { |
| builtPackage, err := b.CMakeBuildPackageWrite(w, bpkg, |
| &linkFlags, &libraries) |
| if err != nil { |
| return err |
| } |
| |
| if builtPackage != nil { |
| builtPackages = append(builtPackages, builtPackage) |
| } |
| } |
| |
| elfName := "cmake_" + filepath.Base(b.AppElfPath()) |
| fmt.Fprintf(w, "# Generating code for %s\n", elfName) |
| |
| var targetObjectsBuffer bytes.Buffer |
| |
| for _, bpkg := range builtPackages { |
| targetObjectsBuffer.WriteString(fmt.Sprintf("%s ", |
| EscapeName(bpkg.rpkg.Lpkg.Name()))) |
| } |
| |
| for _, filename := range libraries { |
| targetObjectsBuffer.WriteString(fmt.Sprintf("%s ", |
| ExtractLibraryName(filename))) |
| } |
| |
| elfOutputDir := replaceBackslashes(trimProjectPath(filepath.Dir(b.AppElfPath()))) |
| fmt.Fprintf(w, "file(WRITE %s \"\")\n", replaceBackslashes(filepath.Join(elfOutputDir, "null.c"))) |
| fmt.Fprintf(w, "add_executable(%s %s)\n\n", elfName, |
| replaceBackslashes(filepath.Join(elfOutputDir, "null.c"))) |
| |
| if c.GetLdResolveCircularDeps() { |
| fmt.Fprintf(w, "target_link_libraries(%s -Wl,--start-group %s -Wl,--end-group)\n", |
| elfName, targetObjectsBuffer.String()) |
| } else { |
| fmt.Fprintf(w, "target_link_libraries(%s %s)\n", |
| elfName, targetObjectsBuffer.String()) |
| } |
| |
| compileFlags = append(compileFlags, c.GetCompilerInfo().Cflags...) |
| compileFlags = append(compileFlags, c.GetLocalCompilerInfo().Cflags...) |
| compileFlags = append(compileFlags, c.GetCompilerInfo().CXXflags...) |
| compileFlags = append(compileFlags, c.GetLocalCompilerInfo().CXXflags...) |
| compileFlags = util.SortFields(compileFlags...) |
| |
| fmt.Fprintf(w, |
| `set_property(TARGET %s APPEND_STRING |
| PROPERTY |
| COMPILE_FLAGS |
| "%s")`, |
| elfName, |
| strings.Join(escapeFlagsSlice(compileFlags), " ")) |
| fmt.Fprintln(w) |
| |
| lFlags := append(c.GetCompilerInfo().Lflags, c.GetLocalCompilerInfo().Lflags...) |
| lFlags = append(lFlags, linkFlags...) |
| lFlags = util.SortFields(lFlags...) |
| |
| for _, ld := range c.LinkerScripts { |
| lFlags = append(lFlags, "-T"+ld) |
| } |
| |
| var cFlags []string |
| cFlags = append(cFlags, c.GetCompilerInfo().Cflags...) |
| cFlags = append(cFlags, c.GetLocalCompilerInfo().Cflags...) |
| cFlags = util.SortFields(cFlags...) |
| lFlags = append(lFlags, cFlags...) |
| |
| var cxxFlags []string |
| cxxFlags = append(cxxFlags, c.GetCompilerInfo().CXXflags...) |
| cxxFlags = append(cxxFlags, c.GetLocalCompilerInfo().CXXflags...) |
| cxxFlags = util.SortFields(cxxFlags...) |
| lFlags = append(lFlags, cxxFlags...) |
| |
| fmt.Fprintf(w, |
| `set_target_properties(%s |
| PROPERTIES |
| ARCHIVE_OUTPUT_DIRECTORY %s |
| LIBRARY_OUTPUT_DIRECTORY %s |
| RUNTIME_OUTPUT_DIRECTORY %s |
| LINK_FLAGS "%s" |
| LINKER_LANGUAGE C)`, |
| elfName, |
| elfOutputDir, |
| elfOutputDir, |
| elfOutputDir, |
| strings.Join(escapeFlagsSlice(lFlags), " ")) |
| |
| fmt.Fprintln(w) |
| |
| libs := strings.Join(getLibsFromLinkerFlags(lFlags), " ") |
| fmt.Fprintf(w, "# Workaround for gcc linker woes\n") |
| fmt.Fprintf(w, "set(CMAKE_C_LINK_EXECUTABLE \"${CMAKE_C_LINK_EXECUTABLE} %s\")\n", libs) |
| fmt.Fprintln(w) |
| |
| return nil |
| } |
| |
| func getLibsFromLinkerFlags(lflags []string) []string { |
| libs := []string{} |
| |
| for _, flag := range lflags { |
| if strings.HasPrefix(flag, "-l") { |
| libs = append(libs, flag) |
| } |
| } |
| |
| return libs |
| } |
| |
| func CmakeCompilerInfoWrite(w io.Writer, archiveFile string, bpkg *BuildPackage, |
| cj toolchain.CompilerJob, otherIncludes []string) { |
| c := cj.Compiler |
| |
| var includes []string |
| |
| includes = append(includes, c.GetCompilerInfo().Includes...) |
| includes = append(includes, c.GetLocalCompilerInfo().Includes...) |
| includes = append(includes, otherIncludes...) |
| |
| // Sort and remove duplicate flags |
| includes = util.SortFields(includes...) |
| trimProjectPathSlice(includes) |
| replaceBackslashesSlice(includes) |
| |
| fmt.Fprintf(w, |
| `set_target_properties(%s |
| PROPERTIES |
| ARCHIVE_OUTPUT_DIRECTORY %s |
| LIBRARY_OUTPUT_DIRECTORY %s |
| RUNTIME_OUTPUT_DIRECTORY %s)`, |
| EscapeName(bpkg.rpkg.Lpkg.Name()), |
| archiveFile, |
| archiveFile, |
| archiveFile, |
| ) |
| fmt.Fprintln(w) |
| fmt.Fprintf(w, "target_include_directories(%s PUBLIC %s)\n\n", |
| EscapeName(bpkg.rpkg.Lpkg.Name()), |
| strings.Join(includes, " ")) |
| } |
| |
| func (t *TargetBuilder) CMakeTargetBuilderWrite(w io.Writer, targetCompiler *toolchain.Compiler) error { |
| if err := t.PrepBuild(); err != nil { |
| return err |
| } |
| |
| if len(t.res.PreBuildCmdCfg.StageFuncs) > 0 || |
| len(t.res.PreLinkCmdCfg.StageFuncs) > 0 || |
| len(t.res.PostLinkCmdCfg.StageFuncs) > 0 { |
| |
| util.OneTimeWarning( |
| "custom commands not included in cmake output (unsupported)") |
| } |
| |
| /* Build the Apps */ |
| project.ResetDeps(t.AppList) |
| |
| targetCompiler.LinkerScripts = t.bspPkg.LinkerScripts |
| |
| if err := t.bspPkg.Reload(t.AppBuilder.cfg.SettingValues()); err != nil { |
| return err |
| } |
| |
| if err := t.AppBuilder.CMakeTargetWrite(w, targetCompiler); err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func CmakeCompilerWrite(w io.Writer, c *toolchain.Compiler) { |
| /* Since CMake 3 it is required to set a full path to the compiler */ |
| /* TODO: get rid of the prefix to /usr/bin */ |
| fmt.Fprintln(w, "set(CMAKE_SYSTEM_NAME Generic)") |
| fmt.Fprintln(w, "set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)") |
| fmt.Fprintf(w, "set(CMAKE_C_COMPILER %s)\n", c.GetCcPath()) |
| fmt.Fprintf(w, "set(CMAKE_CXX_COMPILER %s)\n", c.GetCppPath()) |
| fmt.Fprintf(w, "set(CMAKE_ASM_COMPILER %s)\n", c.GetAsPath()) |
| /* TODO: cmake returns error on link */ |
| //fmt.Fprintf(w, "set(CMAKE_AR %s)\n", c.GetArPath()) |
| fmt.Fprintln(w) |
| } |
| |
| func CmakeHeaderWrite(w io.Writer, c *toolchain.Compiler, targetName string) { |
| fmt.Fprintln(w, "cmake_minimum_required(VERSION 3.7)\n") |
| CmakeCompilerWrite(w, c) |
| fmt.Fprintf(w, "project(%s VERSION 0.0.0 LANGUAGES C CXX ASM)\n\n", targetName) |
| fmt.Fprintln(w, "SET(CMAKE_C_FLAGS_BACKUP \"${CMAKE_C_FLAGS}\")") |
| fmt.Fprintln(w, "SET(CMAKE_CXX_FLAGS_BACKUP \"${CMAKE_CXX_FLAGS}\")") |
| fmt.Fprintln(w, "SET(CMAKE_ASM_FLAGS_BACKUP \"${CMAKE_ASM_FLAGS}\")") |
| fmt.Fprintln(w) |
| } |
| |
| func CMakeTargetGenerate(target *target.Target) error { |
| CmakeFileHandle, err := os.Create(CmakeListsPath()) |
| if err != nil { |
| return util.ChildNewtError(err) |
| } |
| |
| var b = bytes.Buffer{} |
| w := bufio.NewWriter(&b) |
| defer CmakeFileHandle.Close() |
| |
| targetBuilder, err := NewTargetBuilder(target) |
| if err != nil { |
| return err |
| } |
| |
| targetCompiler, err := targetBuilder.NewCompiler("", "") |
| if err != nil { |
| return err |
| } |
| |
| CmakeHeaderWrite(w, targetCompiler, target.ShortName()) |
| |
| if err := targetBuilder.CMakeTargetBuilderWrite(w, targetCompiler); err != nil { |
| return err |
| } |
| |
| w.Flush() |
| |
| CmakeFileHandle.Write(b.Bytes()) |
| return nil |
| } |