Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 81 additions & 15 deletions lib/services/ios-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ export class IOSProjectService
{
private static IOS_PROJECT_NAME_PLACEHOLDER = "__PROJECT_NAME__";
private static IOS_PLATFORM_NAME = "ios";
// CLI-managed folder under the platform root where we write generated
// artifacts (e.g. plugin modulemaps) so we never write into node_modules
private static GENERATED_PLUGINS_DIR_NAME = ".plugins";

constructor(
$fs: IFileSystem,
Expand Down Expand Up @@ -530,7 +533,10 @@ export class IOSProjectService
singlePlatformFramework,
path.extname(singlePlatformFramework),
);
let frameworkBinaryPath = path.join(singlePlatformFramework, frameworkName)
let frameworkBinaryPath = path.join(
singlePlatformFramework,
frameworkName,
);
if (library.BinaryPath) {
frameworkBinaryPath = path.join(
frameworkPath,
Expand All @@ -548,7 +554,9 @@ export class IOSProjectService
frameworkPath,
path.extname(frameworkPath),
);
return await isDynamicFrameworkBundle(path.join(frameworkPath, frameworkName));
return await isDynamicFrameworkBundle(
path.join(frameworkPath, frameworkName),
);
}
}

Expand Down Expand Up @@ -658,7 +666,29 @@ export class IOSProjectService
);
project.addToHeaderSearchPaths({ relativePath: relativeHeaderSearchPath });

this.generateModulemap(headersSubpath, libraryName);
// Write the generated modulemap into a CLI-managed folder under the
// platform root (never into node_modules). The modulemap references the
// plugin's headers in-place via relative paths, so nothing is copied.
const modulemapDir = path.join(
this.getPlatformData(projectData).projectRoot,
IOSProjectService.GENERATED_PLUGINS_DIR_NAME,
libraryName,
);
const hasModulemap = this.generateModulemap(
headersSubpath,
libraryName,
modulemapDir,
);
if (hasModulemap) {
// Put the modulemap dir on the header search path so clang discovers
// the module there instead of inside node_modules.
project.addToHeaderSearchPaths({
relativePath: this.getLibSubpathRelativeToProjectPath(
modulemapDir,
projectData,
),
});
}
this.savePbxProj(project, projectData);
}

Expand Down Expand Up @@ -1682,6 +1712,19 @@ export class IOSProjectService
project.removeFromHeaderSearchPaths({
relativePath: relativeHeaderSearchPath,
});

// Remove the generated modulemap dir search path (see addStaticLibrary)
const modulemapDir = path.join(
this.getPlatformData(projectData).projectRoot,
IOSProjectService.GENERATED_PLUGINS_DIR_NAME,
path.basename(staticLibPath, ".a"),
);
project.removeFromHeaderSearchPaths({
relativePath: this.getLibSubpathRelativeToProjectPath(
modulemapDir,
projectData,
),
});
},
);

Expand All @@ -1691,29 +1734,52 @@ export class IOSProjectService
private generateModulemap(
headersFolderPath: string,
libraryName: string,
): void {
modulemapDir: string,
): boolean {
const modulemapPath = path.join(modulemapDir, "module.modulemap");

// A plugin may ship a `.a` without an `include/{lib}` headers folder. In
// that case there's nothing to expose as a module - clean up any stale
// modulemap and bail out instead of letting readDirectory throw.
if (!this.$fs.exists(headersFolderPath)) {
if (this.$fs.exists(modulemapPath)) {
this.$fs.deleteFile(modulemapPath);
}
return false;
}

const headersFilter = (fileName: string, containingFolderPath: string) =>
path.extname(fileName) === ".h" &&
this.$fs.getFsStats(path.join(containingFolderPath, fileName)).isFile();
const headersFolderContents = this.$fs.readDirectory(headersFolderPath);
let headers = _(headersFolderContents)
.filter((item) => headersFilter(item, headersFolderPath))
.value();
const headerFiles = headersFolderContents.filter((item) =>
headersFilter(item, headersFolderPath),
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if (!headers.length) {
this.$fs.deleteFile(path.join(headersFolderPath, "module.modulemap"));
return;
if (!headerFiles.length) {
if (this.$fs.exists(modulemapPath)) {
this.$fs.deleteFile(modulemapPath);
}
return false;
}

headers = _.map(headers, (value) => `header "${value}"`);
// Reference the plugin's headers (still in node_modules) relative to the
// generated modulemap's location, so we don't copy headers or write into
// node_modules.
const headers = _.map(headerFiles, (value) => {
const relativeHeaderPath = path.relative(
modulemapDir,
path.join(headersFolderPath, value),
);
return `header "${relativeHeaderPath}"`;
});

const modulemap = `module ${libraryName} { explicit module ${libraryName} { ${headers.join(
" ",
)} } }`;
this.$fs.writeFile(
path.join(headersFolderPath, "module.modulemap"),
modulemap,
);
this.$fs.ensureDirectoryExists(modulemapDir);
this.$fs.writeFile(modulemapPath, modulemap);
return true;
}

private async mergeProjectXcconfigFiles(
Expand Down
33 changes: 25 additions & 8 deletions test/ios-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1035,12 +1035,26 @@ describe("Static libraries support", () => {
fs.writeFile(join(staticLibraryHeadersPath, header), "");
});

iOSProjectService.generateModulemap(staticLibraryHeadersPath, libraryName);
// The modulemap is written into a CLI-managed dir (not next to the
// headers / not in node_modules) and references the headers in place.
const modulemapDir = join(projectPath, ".plugins", libraryName);
const generated = iOSProjectService.generateModulemap(
staticLibraryHeadersPath,
libraryName,
modulemapDir,
);
assert.isTrue(generated);

// Read the generated modulemap and verify it.
let modulemap = fs.readFile(
join(staticLibraryHeadersPath, "module.modulemap"),
let modulemap = fs.readFile(join(modulemapDir, "module.modulemap"));
const headerCommands = _.map(
headers,
(value) =>
`header "${path.relative(
modulemapDir,
join(staticLibraryHeadersPath, value),
)}"`,
);
const headerCommands = _.map(headers, (value) => `header "${value}"`);
const modulemapExpectation = `module ${libraryName} { explicit module ${libraryName} { ${headerCommands.join(
" ",
)} } }`;
Expand All @@ -1051,13 +1065,16 @@ describe("Static libraries support", () => {
_.each(headers, (header) => {
fs.deleteFile(join(staticLibraryHeadersPath, header));
});
iOSProjectService.generateModulemap(staticLibraryHeadersPath, libraryName);
const regenerated = iOSProjectService.generateModulemap(
staticLibraryHeadersPath,
libraryName,
modulemapDir,
);
assert.isFalse(regenerated);

let error: any;
try {
modulemap = fs.readFile(
join(staticLibraryHeadersPath, "module.modulemap"),
);
modulemap = fs.readFile(join(modulemapDir, "module.modulemap"));
} catch (err) {
error = err;
}
Expand Down