diff --git a/package-lock.json b/package-lock.json index e50de91..47500a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "eide", - "version": "3.26.0", + "version": "3.26.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "eide", - "version": "3.26.0", + "version": "3.26.5", "license": "MIT", "dependencies": { "iconv-lite": "^0.5.0", diff --git a/src/EIDEProject.ts b/src/EIDEProject.ts index cf7542c..fd9ac69 100644 --- a/src/EIDEProject.ts +++ b/src/EIDEProject.ts @@ -3414,7 +3414,7 @@ $(OUT_DIR): protected abstract create(option: CreateOptions): File; - abstract ExportToKeilProject(): File | undefined; + abstract ExportToKeilProject(saveFile?: File): File | undefined; static NewProject(workspaceState: vscode.Memento): AbstractProject { return new EIDEProject(workspaceState); @@ -3813,7 +3813,7 @@ class EIDEProject extends AbstractProject { return baseInfo.workspaceFile; } - ExportToKeilProject(): File | undefined { + ExportToKeilProject(saveFile?: File): File | undefined { let keilFile: File; @@ -3826,8 +3826,8 @@ class EIDEProject extends AbstractProject { const keilSuffix = prjConfig.type === 'C51' ? 'uvproj' : 'uvprojx'; const suffixFilter = [new RegExp('\\.' + keilSuffix + '$', 'i')]; - // local keil file - const localKeilFile = File.fromArray([this.GetRootDir().path, `${prjConfig.name}.${keilSuffix}`]); + // use user-specified save path, or fall back to project root + const localKeilFile = saveFile ?? File.fromArray([this.GetRootDir().path, `${prjConfig.name}.${keilSuffix}`]); // get from project root folder if (localKeilFile.IsFile()) { @@ -3860,7 +3860,7 @@ class EIDEProject extends AbstractProject { halFiles = halFiles.concat(group.files); } else { fileGroups.push({ - name: File.ToUnixPath(rePath).toUpperCase(), + name: File.ToUnixPath(rePath), files: group.files, disabled: group.disabled }); @@ -3892,10 +3892,13 @@ class EIDEProject extends AbstractProject { // rm empty file groups for MDK fileGroups = fileGroups.filter(g => g.files.length > 0); + const outDir = saveFile ? new File(NodePath.dirname(saveFile.path)) : this.GetRootDir(); + const outName = saveFile ? saveFile.noSuffixName : localKeilFile.noSuffixName; + // set keil xml - keilParser.SetKeilXml(this, fileGroups, cDevice); + keilParser.SetKeilXml(this, fileGroups, outDir, cDevice); - return keilParser.Save(this.GetRootDir(), localKeilFile.noSuffixName); + return keilParser.Save(outDir, outName); } //////////////////////////////// overrride /////////////////////////////////// diff --git a/src/EIDEProjectExplorer.ts b/src/EIDEProjectExplorer.ts index 36ccef9..8bfbfa9 100644 --- a/src/EIDEProjectExplorer.ts +++ b/src/EIDEProjectExplorer.ts @@ -4203,7 +4203,7 @@ export class ProjectExplorer implements CustomConfigurationProvider { } } - ExportKeilXml(prjItem: ProjTreeItem) { + async ExportKeilXml(prjItem: ProjTreeItem) { try { const prj = this.getProjectByTreeItem(prjItem); if (!prj) @@ -4221,7 +4221,26 @@ export class ProjectExplorer implements CustomConfigurationProvider { return; } - const xmlFile = prj.ExportToKeilProject(); + const prjConfig = prj.GetConfiguration().config; + const keilSuffix = prjConfig.type === 'C51' ? 'uvproj' : 'uvprojx'; + const defaultUri = vscode.Uri.file(NodePath.join(prj.GetRootDir().path, `${prjConfig.name}.${keilSuffix}`)); + + const uri = await vscode.window.showSaveDialog({ + defaultUri: defaultUri, + filters: { 'Keil Project': [keilSuffix] } + }); + if (!uri) return; + + // warn if the chosen save path is on a different drive from the project root; + // in that case, cross-drive paths cannot be made relative and will be written as absolute paths + const saveDrive = NodePath.parse(uri.fsPath).root.toLowerCase(); + const prjDrive = NodePath.parse(prj.GetRootDir().path).root.toLowerCase(); + if (saveDrive !== prjDrive) { + GlobalEvent.emit('msg', newMessage('Warning', + `导出路径 (${saveDrive}) 与项目根目录 (${prjDrive}) 不在同一驱动器,此行为可能导致移动或复制项目后 Keil 无法正确识别文件。`)); + } + + const xmlFile = prj.ExportToKeilProject(new File(uri.fsPath)); if (xmlFile) { GlobalEvent.emit('msg', newMessage('Info', export_keil_xml_ok + prj.toRelativePath(xmlFile.path))); diff --git a/src/KeilXmlParser.ts b/src/KeilXmlParser.ts index f6fc5d1..e4098c7 100644 --- a/src/KeilXmlParser.ts +++ b/src/KeilXmlParser.ts @@ -306,7 +306,7 @@ export abstract class KeilParser { abstract ParseData(): KeilParserResult[]; - abstract SetKeilXml(prj: AbstractProject, fileGroups: FileGroup[], deviceInfo?: CurrentDevice): void; + abstract SetKeilXml(prj: AbstractProject, fileGroups: FileGroup[], keilOutputDir: File, deviceInfo?: CurrentDevice): void; } //---- @@ -504,7 +504,7 @@ class C51Parser extends KeilParser { } } - SetKeilXml(prj: AbstractProject, fileGroups: FileGroup[], deviceInfo?: CurrentDevice): void { + SetKeilXml(prj: AbstractProject, fileGroups: FileGroup[], keilOutputDir: File, deviceInfo?: CurrentDevice): void { const prjConfig: ProjectConfiguration = prj.GetConfiguration(); const target = this.doc.Project.Targets.Target[0]; @@ -534,14 +534,15 @@ class C51Parser extends KeilParser { target.TargetOption.TargetCommonOption.Device = devName; target.TargetOption.TargetCommonOption.Vendor = vendor; - const outFolder = File.normalize(prjConfig.config.outDir); - target.TargetOption.TargetCommonOption.OutputDirectory = `.\\${outFolder}\\Keil\\`; - target.TargetOption.TargetCommonOption.ListingPath = `.\\${outFolder}\\Keil\\`; + const outFolderAbs = prj.ToAbsolutePath(prjConfig.config.outDir); + const outFolder = File.ToLocalPath(keilOutputDir.ToRelativePath(outFolderAbs) || outFolderAbs); + target.TargetOption.TargetCommonOption.OutputDirectory = `${outFolder}\\Keil\\`; + target.TargetOption.TargetCommonOption.ListingPath = `${outFolder}\\Keil\\`; target.TargetOption.TargetCommonOption.OutputName = target.TargetName; target.TargetOption.Target51.C51.VariousControls.IncludePath = mergedDep.incList .map(s => prj.resolveEnvVar(s)) - .map(inc => File.ToLocalPath(prj.toRelativePath(inc))) + .map(inc => { const abs = File.isAbsolute(inc) ? inc : prj.ToAbsolutePath(inc); return File.ToLocalPath(keilOutputDir.ToRelativePath(abs) || abs); }) .join(';'); target.TargetOption.Target51.C51.VariousControls.Define = mergedDep.defineList.join(","); @@ -563,7 +564,7 @@ class C51Parser extends KeilParser { const fileElement = { FileName: _f.file.name, FileType: this.getFileType(_f.file).toString(), - FilePath: prj.ToRelativePath(_f.file.path) || _f.file.path + FilePath: File.ToLocalPath(keilOutputDir.ToRelativePath(_f.file.path) || _f.file.path) }; this.setFileDisableFlag(fileElement, _f.disabled); @@ -1169,7 +1170,7 @@ class ARMParser extends KeilParser { return result; } - private setOption(targetOptionObj: any, prj: AbstractProject) { + private setOption(targetOptionObj: any, prj: AbstractProject, keilOutputDir: File) { const armAdsObj = targetOptionObj.TargetArmAds; const prjConfig = >prj.GetConfiguration(); @@ -1213,7 +1214,7 @@ class ARMParser extends KeilParser { LDads.umfTarg = config.useCustomScatterFile ? '0' : '1'; if (config.scatterFilePath) { const absPath = prj.ToAbsolutePath(config.scatterFilePath); - LDads.ScatterFile = this.ToRelativePath(absPath); + LDads.ScatterFile = File.ToLocalPath(keilOutputDir.ToRelativePath(absPath) || absPath); } else { LDads.ScatterFile = ''; } @@ -1317,7 +1318,7 @@ class ARMParser extends KeilParser { } } - SetKeilXml(prj: AbstractProject, fileGroups: FileGroup[], deviceInfo?: CurrentDevice): void { + SetKeilXml(prj: AbstractProject, fileGroups: FileGroup[], keilOutputDir: File, deviceInfo?: CurrentDevice): void { const prjConfig: ProjectConfiguration = prj.GetConfiguration(); const target = this.doc.Project.Targets.Target[0]; @@ -1366,17 +1367,19 @@ class ARMParser extends KeilParser { } target.TargetName = prjConfig.config.name; + target.uAC6 = prj.getToolchain().name === 'AC6' ? '1' : '0'; target.TargetOption.TargetCommonOption.Device = devName; target.TargetOption.TargetCommonOption.Vendor = vendor; - const outFolder = File.normalize(prjConfig.config.outDir); - target.TargetOption.TargetCommonOption.OutputDirectory = `.\\${outFolder}\\Keil\\`; - target.TargetOption.TargetCommonOption.ListingPath = `.\\${outFolder}\\Keil\\`; + const outFolderAbs = prj.ToAbsolutePath(prjConfig.config.outDir); + const outFolder = File.ToLocalPath(keilOutputDir.ToRelativePath(outFolderAbs) || outFolderAbs); + target.TargetOption.TargetCommonOption.OutputDirectory = `${outFolder}\\Keil\\`; + target.TargetOption.TargetCommonOption.ListingPath = `${outFolder}\\Keil\\`; target.TargetOption.TargetCommonOption.OutputName = target.TargetName; target.TargetOption.TargetArmAds.Cads.VariousControls.IncludePath = mergedDep.incList .map(s => prj.resolveEnvVar(s)) - .map(inc => File.ToLocalPath(prj.toRelativePath(inc))) + .map(inc => { const abs = File.isAbsolute(inc) ? inc : prj.ToAbsolutePath(inc); return File.ToLocalPath(keilOutputDir.ToRelativePath(abs) || abs); }) .join(';'); target.TargetOption.TargetArmAds.Cads.VariousControls.Define = mergedDep.defineList.join(","); // C/CPP @@ -1387,7 +1390,7 @@ class ARMParser extends KeilParser { target.TargetOption.TargetArmAds.Aads.VariousControls.Define = defines.join(","); // ASM } - this.setOption(target.TargetOption, prj); + this.setOption(target.TargetOption, prj, keilOutputDir); const nGroups: any[] = []; @@ -1404,7 +1407,7 @@ class ARMParser extends KeilParser { const fileElement = { FileName: _f.file.name, FileType: this.getFileType(_f.file).toString(), - FilePath: prj.ToRelativePath(_f.file.path) || _f.file.path + FilePath: File.ToLocalPath(keilOutputDir.ToRelativePath(_f.file.path) || _f.file.path) }; this.setFileDisableFlag(fileElement, _f.disabled);