Kendryte-dev-extension/src/command/makefileService/writeCmakeList.ts

359 lines
13 KiB
TypeScript

import * as vscode from 'vscode'
import { writeFileSync, readFileSync } from 'fs'
import { fileListReader } from '@command/makefileService/cmakeListFileReader'
import { ProjectInfo, ProjectConfig, FileList } from '@command/makefileService/types'
import { format } from '@utils/index'
import { join } from 'path'
export const createCmakeListFile = (project: ProjectInfo, dependencies?: Array<ProjectInfo>): void => {
if (!process.env.packagePath) {
vscode.window.showErrorMessage('No local dependencies found. Please try to execute reinsall packages command.')
return
}
const fileReader = fileListReader(join(process.env.packagePath, 'cmake-list-files'))
const data = cmakeListTemplete(project.path, project.config, project.isRoot, fileReader, dependencies)
writeFileSync(`${project.path}/CMakeLists.txt`, data)
}
const cmakeListTemplete = (projectPath: string, projectConfig: ProjectConfig, isRoot: boolean, fileReader:FileList, dependencies?: Array<ProjectInfo>): string => {
return (`
# DO NOT MODIFY THIS FILE, IT WILL BE OVERRIDE!!!
# You can set the "automake" options as false on kendryte-package.json to disable auto generate CMakeLists.txt file
# [section] Head
${fileReader.reset}
cmake_minimum_required(VERSION 3.0.0)
set(PROJECT_NAME "${projectConfig.name}")
# [/section] Head
# [section] Init
${isRoot ? rootInitSection(fileReader) : '# not need in sub project'}
${debugModeProperty(projectConfig.debug)}
set(CMAKE_GENERATOR "Unix Makefiles" CACHE INTERNAL "this const is defined by toolchain")
set(CMAKE_GENERATOR_INSTANCE "" CACHE INTERNAL "this const is defined by toolchain")
# [/section] Init
# [section] Project
${fileReader.fix9985}
message("======== PROJECT:\${PROJECT_NAME} ========")
project(\${PROJECT_NAME})
## [section] Header
${includeDirs(projectConfig.header, projectConfig.includeFolders)}
## [/section] Header
## [section] Source
${sourceFiles(projectConfig.source)}
## [/section] Source
# [/section] Project
# [section] Custom
${projectConfig.extraList ? readFileSync(`${projectPath}/${projectConfig.extraList}`, 'utf-8') : ''}
# [/section] Custom
# [section] Target
${createTarget(projectConfig.type, projectConfig.source, projectConfig.name)}
${debugModeValue(projectConfig.debug)}
${setProperties(projectConfig.properties, projectConfig.definitions, dependencies)}
# [/section] Target
# [section] Dependency
${addSubProjects(isRoot, projectConfig.dependencies, projectConfig.localDependency, projectConfig.name)}
# [/section] Dependency
# [section] C/C++ compiler flags
${flags(projectConfig.c_flags, projectConfig.cpp_flags, projectConfig.c_cpp_flags)}
${linkSubProjects(isRoot, dependencies)}
# [/section] C/C++ compiler flags
# [section] Finish
${finishSection(fileReader)}
${flashable(isRoot, projectConfig.type, fileReader)}
# [/section] Finish
# [section] Dump Setting
${fileReader.dumpConfig}
${projectConfig.debug ? 'message("\n ${PROJECT_NAME} :: SOURCE_FILES=${SOURCE_FILES}")' : ''}
# [/section] Dump Setting
`)
}
const rootInitSection = (fileReader: FileList) => {
return [
fileReader.macros,
fileReader.coreFlags,
fileReader.ideSettings,
fileReader.toolchain,
].join('\n')
}
const debugModeProperty = (debug: boolean): string => {
if (!debug) {
return '# debug mode disabled'
}
return format(`
# debug mode enabled
set(-DCMAKE_VERBOSE_MAKEFILE TRUE)
set_property(GLOBAL PROPERTY JOB_POOLS single_debug=1)
`)
}
const spaceArray = (arr: Array<string>) => {
return arr.map(path => ` "\${CMAKE_CURRENT_LIST_DIR}/${path}"`).join('\n')
}
const includeDirs = (header: Array<string>, includeFolders: Array<string>): string => {
let localHeaders = '### from project local\n'
if (header && header.length) {
localHeaders += `
include_directories(
${spaceArray(header)}
)`
}
const ideHeaders = `
### from ide
include_directories("\${CMAKE_CURRENT_LIST_DIR}/config")
include_directories("\${CMAKE_CURRENT_LIST_DIR}/config/includes")
`
let sharedHeaders = '## from project public'
if (includeFolders.length) {
sharedHeaders += `
include_directories(
${spaceArray(includeFolders)}
)`
} else {
sharedHeaders += '## no headers';
}
return localHeaders + '\n' + ideHeaders + sharedHeaders
}
const sourceFiles = (source: Array<string>) => {
if (source.length > 0) {
const addSource = source.map((file) => {
return `add_source_file(${file})`;
});
return `## add source from config json (${source.length} files matched)
${addSource.join('\n')}`;
} else {
return '### project have no source code (and should not have)';
}
}
const createTarget = (type: string, source: Array<string>, name: string): string => {
const ret: Array<string> = [];
if (type === 'library') {
ret.push(`## final create ${name} library`);
if (source.length > 0) {
ret.push('add_library(${PROJECT_NAME} SHARED STATIC ${SOURCE_FILES})');
ret.push('target_compile_definitions(${PROJECT_NAME} PRIVATE "PROJECT_PATH=${CMAKE_CURRENT_LIST_DIR}/")');
} else {
ret.push('add_library(${PROJECT_NAME} SHARED STATIC IMPORTED GLOBAL)');
ret.push('set_property(TARGET ${PROJECT_NAME} PROPERTY IMPORTED_LOCATION');
// if (!this.project.json.prebuilt) {
// throw new PathAttachedError(resolvePath(this.project.path, CMAKE_CONFIG_FILE_NAME), missingJsonField('prebuilt'));
// }
// ret.push(' ' + this.spaceArray([this.project.json.prebuilt]));
ret.push(')');
}
} else {
ret.push(`## final create ${name} executable`);
ret.push('add_executable(${PROJECT_NAME} ${SOURCE_FILES})');
ret.push('target_compile_definitions(${PROJECT_NAME} PRIVATE "PROJECT_PATH=${CMAKE_CURRENT_LIST_DIR}/")');
}
return ret.join('\n');
}
const debugModeValue = (debug: boolean): string => {
if (!debug) {
return '# debug mode disabled';
}
return `# debug mode enabled
set_property(TARGET \${PROJECT_NAME} PROPERTY JOB_POOL_COMPILE single_debug)
set_property(TARGET \${PROJECT_NAME} PROPERTY JOB_POOL_LINK single_debug)`
}
const setProperties = (properties: ProjectConfig["properties"], definitions: ProjectConfig["definitions"] = {}, dependencies: Array<ProjectInfo> = []): string => {
const content = [];
if (properties && Object.keys(properties).length) {
content.push('## set properties');
for (const [key, value] of Object.entries(properties)) {
content.push(`set_target_properties($\{PROJECT_NAME} PROPERTIES ${key} ${JSON.stringify(value)})`)
}
} else {
content.push('## no properties')
}
content.push('## set definitions');
dependencies.map(item => {
Object.assign(definitions, item.config.definitions || {})
})
for (const key in definitions) {
const value = definitions[key]
const id = key.replace(/:raw/g, '')
content.push(`add_compile_definitions(${id}${value ? '=' + value : ''})`);
}
return content.join('\n');
}
const addSubProjects = (isRoot: boolean, dependencyList: ProjectConfig["dependencies"], localDependency: ProjectConfig["localDependency"], projectName: ProjectConfig["name"]): string => {
const lines = ['cmake_policy(SET CMP0079 NEW)']
if (isRoot) {
lines.push('## root project will include all dependencies')
for (const dependency in dependencyList) {
const dir = '${CMAKE_CURRENT_LIST_DIR}/kendryte_libraries/' + dependency
lines.push(`add_subdirectory("${dir}" "${dependency}")`);
}
}
if (localDependency) {
lines.push('## include local dependency')
localDependency.map(dependency => {
const dir = join('${CMAKE_CURRENT_LIST_DIR}', dependency).replace(/\\/g, '/')
lines.push(`add_subdirectory("${dir}" "${projectName}_${dependency}")`)
})
}
// Simple Folder Config
// if (this.project.directDependency.length) {
// lines.push('## add simple folder dependency');
// for (const { objectName, path, isSimpleFolder } of this.project.directDependency) {
// if (!isSimpleFolder) {
// continue;
// }
// const rel = relativePath(this.project.path, path);
// const dir = CMAKE_CWD + '/' + rel;
// lines.push(`add_subdirectory(${JSON.stringify(dir)} ${JSON.stringify(objectName)})`);
// }
// }
return lines.join('\n');
}
const flags = (c_flags?: Array<string>, cpp_flags?: Array<string>, c_cpp_flags?: Array<string>): string => {
const map = (...from: (ReadonlyArray<string> | undefined)[]) => {
const args: string[] = [''].concat(...from.map(a => a ? a.slice() : [])).filter(e => !!e)
const arr = args || []
if (arr.length === 0) {
return ''
}
return arr.map(item => ` ${JSON.stringify(item)}`).join('\n')
};
const append = (str: string, varName: string) => {
if (str) {
content.push(`set(FLAGS_FOR_${varName}\n${str}\n)`)
}
return !!str
};
const content: string[] = []
content.push('')
content.push('##### flags from config json #####')
content.push('message("config flags for ${PROJECT_NAME}")')
const editCFlags = append(map(c_flags, c_cpp_flags), 'C')
const editCXXFlags = append(map(cpp_flags, c_cpp_flags), 'CXX')
if (editCFlags || editCXXFlags) {
content.push(`target_compile_options(\${PROJECT_NAME} PRIVATE`)
if (editCFlags) {
content.push(`\t$<$<COMPILE_LANGUAGE:C>:\${FLAGS_FOR_C}>`)
}
if (editCXXFlags) {
content.push(`\t$<$<COMPILE_LANGUAGE:CXX>:\${FLAGS_FOR_CXX}>`)
}
content.push(')')
}
return content.join('\n')
}
const linkSubProjects = (isRoot: boolean, dependencies?: Array<ProjectInfo>): string => {
if (!isRoot || !dependencies) {
return '';
}
interface LinkArguments {
options: Array<string>
objects: Array<string>
}
const linkArguments: LinkArguments = {
options: [
// Default flags
"-nostartfiles",
"-Wl,--gc-sections"
],
objects: []
}
dependencies.map(dependency => {
// Options(Link arguments and ld file)
if (dependency.config.ld_file) {
linkArguments.options.unshift('-T', `\${CMAKE_CURRENT_LIST_DIR}/kendryte_libraries/${dependency.config.name}/${dependency.config.ld_file}`)
}
if (dependency.config.link_flags) {
linkArguments.options = dependency.config.link_flags.concat(linkArguments.options)
}
// Objects(LinkArgumentPrefix PackageName LinkArgumentSuffix and SystemLibrary)
// Add "\"" string on arguments
const addQuot = (list: Array<string> = []): Array<string> => {
list.map((item, index) => {
list[index] = `"${item}"`
})
return list
}
dependency.config.linkArgumentPrefix = addQuot(dependency.config.linkArgumentPrefix)
dependency.config.linkArgumentSuffix = addQuot(dependency.config.linkArgumentSuffix)
dependency.config.systemLibrary = addQuot(dependency.config.systemLibrary)
// Add library name
linkArguments.objects.push(`## ${dependency.config.name}`)
// Add system library options first
linkArguments.objects = linkArguments.objects.concat(dependency.config.systemLibrary)
// if (!dependency.config.linkArgumentPrefix && !dependency.config.systemLibrary && !dependency.config.linkArgumentSuffix) {
// linkArguments.objects.push('No link component')
// return
// }
linkArguments.objects = linkArguments.objects.concat(dependency.config.linkArgumentPrefix)
linkArguments.objects = linkArguments.objects.concat(`"${dependency.config.name}"`)
linkArguments.objects = linkArguments.objects.concat(dependency.config.linkArgumentSuffix)
})
const p1 = linkArguments.options.map(line => {
return `"${line}"`
}).join('\n\t').trim()
const p2 = linkArguments.objects.map(line => {
return line
}).join('\n\t').trim()
let ret = '';
// Link Arugments
if (p1) {
ret += `target_link_options(\${PROJECT_NAME} PUBLIC\n\t${p1}\n)\n`;
}
// LinkArgumentPrefix PackageName LinkArgumentSuffix systemLibrary
if (p2) {
ret += `target_link_libraries(\${PROJECT_NAME} PUBLIC -Wl,--start-group\n\t${p2}\n-Wl,--end-group)\n`;
}
return ret;
}
const finishSection = (fileReader: FileList) =>{
return [
fileReader.afterProject,
"### include(fix9985)",
fileReader.fix9985,
].join('\n')
}
const flashable = (isRoot: boolean, type: string, fileReader: FileList) => {
if (isRoot && type !== 'library') {
return fileReader.flash
} else {
return ''
}
}