From 0901d334d9d1282f0522de2ec55e97f71adc7abc Mon Sep 17 00:00:00 2001 From: Kanami Date: Mon, 23 Mar 2026 17:21:30 +0800 Subject: [PATCH 1/4] fix: correct paths in Keil export when output dir differs from EIDE project root - prompt user to choose the export path via save dialog - compute all paths (includes, source files, scatter file, output dir) relative to the chosen Keil project directory instead of the EIDE root --- src/EIDEProject.ts | 15 +++++++++------ src/EIDEProjectExplorer.ts | 23 +++++++++++++++++++++-- src/KeilXmlParser.ts | 34 ++++++++++++++++++---------------- 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/src/EIDEProject.ts b/src/EIDEProject.ts index cf7542c..5eb08a2 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()) { @@ -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..109602d 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]; @@ -1369,14 +1370,15 @@ class ARMParser 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.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 +1389,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 +1406,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); From c0594d2a2dbecb237c80afb538ed2f6f345db34a Mon Sep 17 00:00:00 2001 From: Kanami Date: Mon, 23 Mar 2026 18:47:07 +0800 Subject: [PATCH 2/4] fix: set uAC6 flag correctly when exporting Keil project with AC6 toolchain Previously uAC6 was always left as the template default (0/AC5), causing Keil to open the exported project with the wrong compiler. --- src/KeilXmlParser.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/KeilXmlParser.ts b/src/KeilXmlParser.ts index 109602d..e4098c7 100644 --- a/src/KeilXmlParser.ts +++ b/src/KeilXmlParser.ts @@ -1367,6 +1367,7 @@ 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; From 370b55d57d88e81ea16cb0b440003fe8121f8ea4 Mon Sep 17 00:00:00 2001 From: Kanami Date: Mon, 23 Mar 2026 19:29:53 +0800 Subject: [PATCH 3/4] fix: preserve original case of group names in Keil export --- src/EIDEProject.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EIDEProject.ts b/src/EIDEProject.ts index 5eb08a2..fd9ac69 100644 --- a/src/EIDEProject.ts +++ b/src/EIDEProject.ts @@ -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 }); From cd1bfa34f38759c244623b24f1f67524cdb393e9 Mon Sep 17 00:00:00 2001 From: Kanami Date: Mon, 23 Mar 2026 19:50:11 +0800 Subject: [PATCH 4/4] chore: sync version num with package.json --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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",