Skip to content

Commit 4cc5ba1

Browse files
authored
feat(ios): manage generated modulemaps in CLI-controlled directory instead of node_modules (#6063)
1 parent 86cb6a5 commit 4cc5ba1

2 files changed

Lines changed: 106 additions & 23 deletions

File tree

lib/services/ios-project-service.ts

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ export class IOSProjectService
9797
{
9898
private static IOS_PROJECT_NAME_PLACEHOLDER = "__PROJECT_NAME__";
9999
private static IOS_PLATFORM_NAME = "ios";
100+
// CLI-managed folder under the platform root where we write generated
101+
// artifacts (e.g. plugin modulemaps) so we never write into node_modules
102+
private static GENERATED_PLUGINS_DIR_NAME = ".plugins";
100103

101104
constructor(
102105
$fs: IFileSystem,
@@ -530,7 +533,10 @@ export class IOSProjectService
530533
singlePlatformFramework,
531534
path.extname(singlePlatformFramework),
532535
);
533-
let frameworkBinaryPath = path.join(singlePlatformFramework, frameworkName)
536+
let frameworkBinaryPath = path.join(
537+
singlePlatformFramework,
538+
frameworkName,
539+
);
534540
if (library.BinaryPath) {
535541
frameworkBinaryPath = path.join(
536542
frameworkPath,
@@ -548,7 +554,9 @@ export class IOSProjectService
548554
frameworkPath,
549555
path.extname(frameworkPath),
550556
);
551-
return await isDynamicFrameworkBundle(path.join(frameworkPath, frameworkName));
557+
return await isDynamicFrameworkBundle(
558+
path.join(frameworkPath, frameworkName),
559+
);
552560
}
553561
}
554562

@@ -658,7 +666,29 @@ export class IOSProjectService
658666
);
659667
project.addToHeaderSearchPaths({ relativePath: relativeHeaderSearchPath });
660668

661-
this.generateModulemap(headersSubpath, libraryName);
669+
// Write the generated modulemap into a CLI-managed folder under the
670+
// platform root (never into node_modules). The modulemap references the
671+
// plugin's headers in-place via relative paths, so nothing is copied.
672+
const modulemapDir = path.join(
673+
this.getPlatformData(projectData).projectRoot,
674+
IOSProjectService.GENERATED_PLUGINS_DIR_NAME,
675+
libraryName,
676+
);
677+
const hasModulemap = this.generateModulemap(
678+
headersSubpath,
679+
libraryName,
680+
modulemapDir,
681+
);
682+
if (hasModulemap) {
683+
// Put the modulemap dir on the header search path so clang discovers
684+
// the module there instead of inside node_modules.
685+
project.addToHeaderSearchPaths({
686+
relativePath: this.getLibSubpathRelativeToProjectPath(
687+
modulemapDir,
688+
projectData,
689+
),
690+
});
691+
}
662692
this.savePbxProj(project, projectData);
663693
}
664694

@@ -1682,6 +1712,19 @@ export class IOSProjectService
16821712
project.removeFromHeaderSearchPaths({
16831713
relativePath: relativeHeaderSearchPath,
16841714
});
1715+
1716+
// Remove the generated modulemap dir search path (see addStaticLibrary)
1717+
const modulemapDir = path.join(
1718+
this.getPlatformData(projectData).projectRoot,
1719+
IOSProjectService.GENERATED_PLUGINS_DIR_NAME,
1720+
path.basename(staticLibPath, ".a"),
1721+
);
1722+
project.removeFromHeaderSearchPaths({
1723+
relativePath: this.getLibSubpathRelativeToProjectPath(
1724+
modulemapDir,
1725+
projectData,
1726+
),
1727+
});
16851728
},
16861729
);
16871730

@@ -1691,29 +1734,52 @@ export class IOSProjectService
16911734
private generateModulemap(
16921735
headersFolderPath: string,
16931736
libraryName: string,
1694-
): void {
1737+
modulemapDir: string,
1738+
): boolean {
1739+
const modulemapPath = path.join(modulemapDir, "module.modulemap");
1740+
1741+
// A plugin may ship a `.a` without an `include/{lib}` headers folder. In
1742+
// that case there's nothing to expose as a module - clean up any stale
1743+
// modulemap and bail out instead of letting readDirectory throw.
1744+
if (!this.$fs.exists(headersFolderPath)) {
1745+
if (this.$fs.exists(modulemapPath)) {
1746+
this.$fs.deleteFile(modulemapPath);
1747+
}
1748+
return false;
1749+
}
1750+
16951751
const headersFilter = (fileName: string, containingFolderPath: string) =>
16961752
path.extname(fileName) === ".h" &&
16971753
this.$fs.getFsStats(path.join(containingFolderPath, fileName)).isFile();
16981754
const headersFolderContents = this.$fs.readDirectory(headersFolderPath);
1699-
let headers = _(headersFolderContents)
1700-
.filter((item) => headersFilter(item, headersFolderPath))
1701-
.value();
1755+
const headerFiles = headersFolderContents.filter((item) =>
1756+
headersFilter(item, headersFolderPath),
1757+
);
17021758

1703-
if (!headers.length) {
1704-
this.$fs.deleteFile(path.join(headersFolderPath, "module.modulemap"));
1705-
return;
1759+
if (!headerFiles.length) {
1760+
if (this.$fs.exists(modulemapPath)) {
1761+
this.$fs.deleteFile(modulemapPath);
1762+
}
1763+
return false;
17061764
}
17071765

1708-
headers = _.map(headers, (value) => `header "${value}"`);
1766+
// Reference the plugin's headers (still in node_modules) relative to the
1767+
// generated modulemap's location, so we don't copy headers or write into
1768+
// node_modules.
1769+
const headers = _.map(headerFiles, (value) => {
1770+
const relativeHeaderPath = path.relative(
1771+
modulemapDir,
1772+
path.join(headersFolderPath, value),
1773+
);
1774+
return `header "${relativeHeaderPath}"`;
1775+
});
17091776

17101777
const modulemap = `module ${libraryName} { explicit module ${libraryName} { ${headers.join(
17111778
" ",
17121779
)} } }`;
1713-
this.$fs.writeFile(
1714-
path.join(headersFolderPath, "module.modulemap"),
1715-
modulemap,
1716-
);
1780+
this.$fs.ensureDirectoryExists(modulemapDir);
1781+
this.$fs.writeFile(modulemapPath, modulemap);
1782+
return true;
17171783
}
17181784

17191785
private async mergeProjectXcconfigFiles(

test/ios-project-service.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,12 +1035,26 @@ describe("Static libraries support", () => {
10351035
fs.writeFile(join(staticLibraryHeadersPath, header), "");
10361036
});
10371037

1038-
iOSProjectService.generateModulemap(staticLibraryHeadersPath, libraryName);
1038+
// The modulemap is written into a CLI-managed dir (not next to the
1039+
// headers / not in node_modules) and references the headers in place.
1040+
const modulemapDir = join(projectPath, ".plugins", libraryName);
1041+
const generated = iOSProjectService.generateModulemap(
1042+
staticLibraryHeadersPath,
1043+
libraryName,
1044+
modulemapDir,
1045+
);
1046+
assert.isTrue(generated);
1047+
10391048
// Read the generated modulemap and verify it.
1040-
let modulemap = fs.readFile(
1041-
join(staticLibraryHeadersPath, "module.modulemap"),
1049+
let modulemap = fs.readFile(join(modulemapDir, "module.modulemap"));
1050+
const headerCommands = _.map(
1051+
headers,
1052+
(value) =>
1053+
`header "${path.relative(
1054+
modulemapDir,
1055+
join(staticLibraryHeadersPath, value),
1056+
)}"`,
10421057
);
1043-
const headerCommands = _.map(headers, (value) => `header "${value}"`);
10441058
const modulemapExpectation = `module ${libraryName} { explicit module ${libraryName} { ${headerCommands.join(
10451059
" ",
10461060
)} } }`;
@@ -1051,13 +1065,16 @@ describe("Static libraries support", () => {
10511065
_.each(headers, (header) => {
10521066
fs.deleteFile(join(staticLibraryHeadersPath, header));
10531067
});
1054-
iOSProjectService.generateModulemap(staticLibraryHeadersPath, libraryName);
1068+
const regenerated = iOSProjectService.generateModulemap(
1069+
staticLibraryHeadersPath,
1070+
libraryName,
1071+
modulemapDir,
1072+
);
1073+
assert.isFalse(regenerated);
10551074

10561075
let error: any;
10571076
try {
1058-
modulemap = fs.readFile(
1059-
join(staticLibraryHeadersPath, "module.modulemap"),
1060-
);
1077+
modulemap = fs.readFile(join(modulemapDir, "module.modulemap"));
10611078
} catch (err) {
10621079
error = err;
10631080
}

0 commit comments

Comments
 (0)