Cleanup Folders after codegen (#33891)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/33891

This diff adds a function to clean up folders and empty files (if any) after the codegen runs in the OSS.
To align how iOS and Android behave in the codegen, we now have to preemptively create folders that we may not need later.
Thanks to this function, we recursively the folders in the codegen and we delete them if they are empty.

## Changelog
[iOS][Added] - Add function to cleanup codegen folders

Reviewed By: cortinico

Differential Revision: D36594999

fbshipit-source-id: 904aa2442c0d9621b7ec77c31f7be9daa4e7b7e1
This commit is contained in:
Riccardo Cipolleschi
2022-05-23 08:27:44 -07:00
committed by Facebook GitHub Bot
parent 5f3c5aa529
commit 71692889b0
2 changed files with 260 additions and 0 deletions
@@ -201,3 +201,228 @@ describe('extractLibrariesFromJSON', () => {
});
});
});
describe('delete empty files and folders', () => {
beforeEach(() => {
jest.resetModules();
});
it('when path is empty file, deletes it', () => {
const targetFilepath = 'my-file.txt';
let statSyncInvocationCount = 0;
let rmSyncInvocationCount = 0;
let rmdirSyncInvocationCount = 0;
jest.mock('fs', () => ({
statSync: filepath => {
statSyncInvocationCount += 1;
expect(filepath).toBe(targetFilepath);
return {
isFile: () => {
return true;
},
size: 0,
};
},
rmSync: filepath => {
rmSyncInvocationCount += 1;
expect(filepath).toBe(targetFilepath);
},
rmdirSync: filepath => {
rmdirSyncInvocationCount += 1;
},
}));
underTest._cleanupEmptyFilesAndFolders(targetFilepath);
expect(statSyncInvocationCount).toBe(1);
expect(rmSyncInvocationCount).toBe(1);
expect(rmdirSyncInvocationCount).toBe(0);
});
it('when path is not an empty file, does nothing', () => {
const targetFilepath = 'my-file.txt';
const size = 128;
let statSyncInvocationCount = 0;
let rmSyncInvocationCount = 0;
let rmdirSyncInvocationCount = 0;
jest.mock('fs', () => ({
statSync: filepath => {
statSyncInvocationCount += 1;
expect(filepath).toBe(targetFilepath);
return {
isFile: () => {
return true;
},
size: size,
};
},
rmSync: filepath => {
rmSyncInvocationCount += 1;
},
rmdirSync: filepath => {
rmdirSyncInvocationCount += 1;
},
}));
underTest._cleanupEmptyFilesAndFolders(targetFilepath);
expect(statSyncInvocationCount).toBe(1);
expect(rmSyncInvocationCount).toBe(0);
expect(rmdirSyncInvocationCount).toBe(0);
});
it("when path is folder and it's empty, removes it", () => {
const targetFolder = 'build/';
const content = [];
let statSyncInvocationCount = 0;
let readdirInvocationCount = 0;
let rmSyncInvocationCount = 0;
let rmdirSyncInvocationCount = 0;
jest.mock('fs', () => ({
statSync: filepath => {
statSyncInvocationCount += 1;
expect(filepath).toBe(targetFolder);
return {
isFile: () => {
return false;
},
};
},
rmSync: filepath => {
rmSyncInvocationCount += 1;
},
rmdirSync: filepath => {
rmdirSyncInvocationCount += 1;
expect(filepath).toBe(targetFolder);
},
readdirSync: filepath => {
readdirInvocationCount += 1;
return content;
},
}));
underTest._cleanupEmptyFilesAndFolders(targetFolder);
expect(statSyncInvocationCount).toBe(1);
expect(readdirInvocationCount).toBe(2);
expect(rmSyncInvocationCount).toBe(0);
expect(rmdirSyncInvocationCount).toBe(1);
});
it("when path is folder and it's not empty, removes only empty folders and files", () => {
const targetFolder = 'build/';
const content = ['emptyFolder', 'emptyFile', 'notEmptyFile'];
const files = ['build/emptyFile', 'build/notEmptyFile'];
const emptyContent = [];
const fileSizes = {
'build/emptyFile': 0,
'build/notEmptyFile': 32,
};
let statSyncInvocation = [];
let rmSyncInvocation = [];
let rmdirSyncInvocation = [];
let readdirInvocation = [];
jest.mock('fs', () => ({
statSync: filepath => {
statSyncInvocation.push(filepath);
return {
isFile: () => {
return files.includes(filepath);
},
size: fileSizes[filepath],
};
},
rmSync: filepath => {
rmSyncInvocation.push(filepath);
},
rmdirSync: filepath => {
rmdirSyncInvocation.push(filepath);
},
readdirSync: filepath => {
readdirInvocation.push(filepath);
return filepath === targetFolder ? content : emptyContent;
},
}));
underTest._cleanupEmptyFilesAndFolders(targetFolder);
expect(statSyncInvocation).toEqual([
'build/',
'build/emptyFolder',
'build/emptyFile',
'build/notEmptyFile',
]);
expect(readdirInvocation).toEqual([
'build/',
'build/emptyFolder',
'build/emptyFolder',
'build/',
]);
expect(rmSyncInvocation).toEqual(['build/emptyFile']);
expect(rmdirSyncInvocation).toEqual(['build/emptyFolder']);
});
it('when path is folder and it contains only empty folders, removes everything', () => {
const targetFolder = 'build/';
const content = ['emptyFolder1', 'emptyFolder2'];
const emptyContent = [];
let statSyncInvocation = [];
let rmSyncInvocation = [];
let rmdirSyncInvocation = [];
let readdirInvocation = [];
jest.mock('fs', () => ({
statSync: filepath => {
statSyncInvocation.push(filepath);
return {
isFile: () => {
return false;
},
};
},
rmSync: filepath => {
rmSyncInvocation.push(filepath);
},
rmdirSync: filepath => {
rmdirSyncInvocation.push(filepath);
},
readdirSync: filepath => {
readdirInvocation.push(filepath);
return filepath === targetFolder
? content.filter(
element =>
!rmdirSyncInvocation.includes(path.join(targetFolder, element)),
)
: emptyContent;
},
}));
underTest._cleanupEmptyFilesAndFolders(targetFolder);
expect(statSyncInvocation).toEqual([
'build/',
'build/emptyFolder1',
'build/emptyFolder2',
]);
expect(readdirInvocation).toEqual([
'build/',
'build/emptyFolder1',
'build/emptyFolder1',
'build/emptyFolder2',
'build/emptyFolder2',
'build/',
]);
expect(rmSyncInvocation).toEqual([]);
expect(rmdirSyncInvocation).toEqual([
'build/emptyFolder1',
'build/emptyFolder2',
'build/',
]);
});
});
@@ -355,6 +355,39 @@ function createComponentProvider(
}
}
// It removes all the empty files and empty folders
// it finds, starting from `filepath`, recursively.
//
// This function is needed since, after aligning the codegen between
// iOS and Android, we have to create empty folders in advance and
// we don't know wheter they will be populated up until the end of the process.
//
// @parameter filepath: the root path from which we want to remove the empty files and folders.
function cleanupEmptyFilesAndFolders(filepath) {
const stats = fs.statSync(filepath);
if (stats.isFile() && stats.size === 0) {
fs.rmSync(filepath);
return;
} else if (stats.isFile()) {
return;
}
const dirContent = fs.readdirSync(filepath);
dirContent.forEach(contentPath =>
cleanupEmptyFilesAndFolders(path.join(filepath, contentPath)),
);
// The original folder may be filled with empty folders
// if that the case, we would also like to remove the parent.
// Hence, we need to read the folder again.
const newContent = fs.readdirSync(filepath);
if (newContent.length === 0) {
fs.rmdirSync(filepath);
return;
}
}
// Execute
/**
@@ -428,6 +461,7 @@ function execute(
);
createComponentProvider(fabricEnabled, schemaPaths, node, iosOutputDir);
cleanupEmptyFilesAndFolders(iosOutputDir);
} catch (err) {
console.error(err);
process.exitCode = 1;
@@ -443,4 +477,5 @@ module.exports = {
_extractLibrariesFromJSON: extractLibrariesFromJSON,
_executeNodeScript: executeNodeScript,
_generateCode: generateCode,
_cleanupEmptyFilesAndFolders: cleanupEmptyFilesAndFolders,
};