@@ -30,6 +30,56 @@ const parser = require('./parser/pbxproj');
3030const plist = require ( 'simple-plist' ) ;
3131const COMMENT_KEY = / _ c o m m e n t $ / ;
3232
33+ // MARK: Start of Typings
34+
35+ /**
36+ * A list of known build phase types.
37+ * @typedef {'PBXCopyFilesBuildPhase' |
38+ * 'PBXResourcesBuildPhase' |
39+ * 'PBXFrameworksBuildPhase' |
40+ * 'PBXHeadersBuildPhase' |
41+ * 'PBXShellScriptBuildPhase' |
42+ * 'PBXSourcesBuildPhase'
43+ * } BuildPhaseType
44+ */
45+
46+ /**
47+ * The result object returned when adding a build phase.
48+ * @typedef {object } AddBuildPhaseResults
49+ * @property {string } uuid Build Phase UUID
50+ * @property {object } buildPhase Build Phase object
51+ */
52+
53+ /**
54+ * A list of valid target types used for copy files build configurations.
55+ * @typedef {'application'
56+ * | 'app_extension'
57+ * | 'bundle'
58+ * | 'command_line_tool'
59+ * | 'dynamic_library'
60+ * | 'framework'
61+ * | 'frameworks'
62+ * | 'static_library'
63+ * | 'unit_test_bundle'
64+ * | 'watch_app'
65+ * | 'watch2_app'
66+ * | 'watch_extension'
67+ * | 'watch2_extension'
68+ * } CopyFilesTargetType
69+ */
70+
71+ /**
72+ * Options used when adding a PBXShellScriptBuildPhase.
73+ * @typedef {object } ShellScriptOptions
74+ * @property {string[] } inputPaths input paths
75+ * @property {string[] } outputPaths output Paths
76+ * @property {string } shellPath shell paths
77+ * @property {0 | 1 } runOnlyForDeploymentPostprocessing run script for install builds only
78+ * @property {1 } [alwaysOutOfDate] disables dependency analysis when set to true
79+ */
80+
81+ // MARK: End of Typings
82+
3383function PBXProject ( filename ) {
3484 if ( ! ( this instanceof PBXProject ) ) { return new PBXProject ( filename ) ; }
3585
@@ -851,6 +901,25 @@ PBXProject.prototype.addTargetDependency = function (target, dependencyTargets)
851901 return { uuid : target , target : nativeTargets [ target ] } ;
852902} ;
853903
904+ /**
905+ * Add files to specific build phase by defining the build phase type.
906+ *
907+ * @example
908+ * // Adding a file to Resource Build Phase.
909+ * myProject.addBuildPhase(
910+ * ['MyAssets.xcassets'],
911+ * 'PBXResourcesBuildPhase',
912+ * 'Resources'
913+ * );
914+ *
915+ * @param {string[] } filePathsArray collection of file paths
916+ * @param {BuildPhaseType } buildPhaseType target build phase
917+ * @param {string } comment name of the build phase. (e.g. "Copy My Files")
918+ * @param {string } target UUID of the PBXNativeTarget (defaults to first target)
919+ * @param {CopyFilesTargetType|ShellScriptOptions } optionsOrFolderType either sell script options or copy files target type
920+ * @param {string } subfolderPath copy files subfolder path (default: "")
921+ * @returns {AddBuildPhaseResults } object containing the build phase & uuid
922+ */
854923PBXProject . prototype . addBuildPhase = function ( filePathsArray , buildPhaseType , comment , target , optionsOrFolderType , subfolderPath ) {
855924 let buildPhaseSection ;
856925 const fileReferenceSection = this . pbxFileReferenceSection ( ) ;
@@ -896,18 +965,33 @@ PBXProject.prototype.addBuildPhase = function (filePathsArray, buildPhaseType, c
896965 const buildFile = buildFileSection [ buildFileKey ] ;
897966 const fileReference = fileReferenceSection [ buildFile . fileRef ] ;
898967
899- if ( ! fileReference ) continue ;
968+ // If a file reference does not define either the name or path property,
969+ // it will not be included to the filePathToBuildFile lookup table.
970+ // If the file reference defines both properies, the path property will
971+ // take priority.
972+ const fileIdentifier = fileReference ?. path || fileReference ?. name ;
900973
901- const pbxFileObj = new PBXFile ( fileReference . path ) ;
974+ if ( ! fileIdentifier ) {
975+ continue ;
976+ }
902977
903- filePathToBuildFile [ fileReference . path ] = { uuid : buildFileKey , basename : pbxFileObj . basename , group : pbxFileObj . group } ;
978+ const pbxFileObj = new PBXFile ( fileIdentifier ) ;
979+
980+ // Creates a lookup table of existing PBXBuildFile entries keyed by fileIdentifier.
981+ // Used later when adding files to the build phase to reuse the existing PBXBuildFile
982+ // instead of generating new PBXBuildFile.
983+ filePathToBuildFile [ fileIdentifier ] = {
984+ uuid : buildFileKey ,
985+ basename : pbxFileObj . basename ,
986+ group : pbxFileObj . group
987+ } ;
904988 }
905989
906990 for ( let index = 0 ; index < filePathsArray . length ; index ++ ) {
907991 const filePath = filePathsArray [ index ] ;
908992 const filePathQuoted = '"' + filePath + '"' ;
909- const file = new PBXFile ( filePath ) ;
910993
994+ // Checks for an existing PBXBuildFile entry from the filePathToBuildFile lookup table.
911995 if ( filePathToBuildFile [ filePath ] ) {
912996 buildPhase . files . push ( pbxBuildPhaseObj ( filePathToBuildFile [ filePath ] ) ) ;
913997 continue ;
@@ -916,10 +1000,16 @@ PBXProject.prototype.addBuildPhase = function (filePathsArray, buildPhaseType, c
9161000 continue ;
9171001 }
9181002
1003+ // If no PBXBuildFile entry is found in the lookup table, a new entry is created
1004+ // with a new UUID. It is then:
1005+ // 1. Added to the PBXFileReferenceSection
1006+ // 2. Added to the PBXBuildFileSection
1007+ // 3. Pushed onto the build phase file list.
1008+ const file = new PBXFile ( filePath ) ;
9191009 file . uuid = this . generateUuid ( ) ;
9201010 file . fileRef = this . generateUuid ( ) ;
921- this . addToPbxFileReferenceSection ( file ) ; // PBXFileReference
922- this . addToPbxBuildFileSection ( file ) ; // PBXBuildFile
1011+ this . addToPbxFileReferenceSection ( file ) ;
1012+ this . addToPbxBuildFileSection ( file ) ;
9231013 buildPhase . files . push ( pbxBuildPhaseObj ( file ) ) ;
9241014 }
9251015
0 commit comments