Skip to content

Commit

Permalink
Cocoapods subspec consumption: Introduce script to filter out unused …
Browse files Browse the repository at this point in the history
…resources. (#492)

* First version of the resource cleanup script based on .resources.xcfilelist files.

* Fixing script phase commands and preparing for another test (tag 0.0.13_Test).

* Fixing log messages and setting additional environment variables in the xcodebuild command.

* Bumping version to 0.0.14

* Reverting podspec version and linting script file.

* Fixing indentation of string extension.

* Adding flags to set verbose level based on Debug/Release configurations.

* Setting the ARCHS variable to prevent the build warning in debug mode:

Showing All Messages
ONLY_ACTIVE_ARCH=YES requested with multiple ARCHS and no run destination to provide an active architecture; building for all applicable architectures (in target 'MicrosoftFluentUI-FluentUIResources-ios' from project 'Pods')

* Fixing pod lib lint issues by using $PODS_TARGET_SRCROOT so it can find the script during linting.
Enclosing variables in double quotes to prevent parameters from being interpreted incorrectly when they have spaces.
  • Loading branch information
rdeassis authored Mar 30, 2021
1 parent 4bd410e commit 5f53813
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 0 deletions.
26 changes: 26 additions & 0 deletions MicrosoftFluentUI.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Pod::Spec.new do |s|
s.subspec 'Avatar_ios' do |avatar_ios|
avatar_ios.platform = :ios
avatar_ios.dependency 'MicrosoftFluentUI/Core_ios'
avatar_ios.preserve_paths = ["ios/FluentUI/Avatar/Avatar.resources.xcfilelist"]
avatar_ios.source_files = ["ios/FluentUI/Avatar/**/*.{swift,h}"]
end

Expand All @@ -41,6 +42,7 @@ Pod::Spec.new do |s|
s.subspec 'BarButtonItems_ios' do |barbuttonitems_ios|
barbuttonitems_ios.platform = :ios
barbuttonitems_ios.dependency 'MicrosoftFluentUI/Core_ios'
barbuttonitems_ios.preserve_paths = ["ios/FluentUI/BarButtonItems/BarButtonItems.resources.xcfilelist"]
barbuttonitems_ios.source_files = ["ios/FluentUI/BarButtonItems/**/*.{swift,h}"]
end

Expand Down Expand Up @@ -79,6 +81,23 @@ Pod::Spec.new do |s|
core_ios.platform = :ios
core_ios.resource_bundle = { 'FluentUIResources-ios' => ["apple/Resources/**/*.{json,xcassets}",
"ios/FluentUI/**/*.{storyboard,xib,xcassets,strings,stringsdict}"] }
core_ios.script_phase = { :name => 'Optimize resource bundle',
:script => 'echo "=== Removing unused resources from FluentUI-ios.xcassets ==="
XCODEBUILDPARAMS="-quiet"
if [ "${CONFIGURATION}" = "Debug" ]; then
CONDITIONALCOMPILATIONFLAGS="-D VERBOSE_OUTPUT"
XCODEBUILDPARAMS=""
fi
xcrun --sdk macosx swift ${CONDITIONALCOMPILATIONFLAGS} ${PODS_TARGET_SRCROOT}/scripts/removeUnusedResourcesFromAssets.swift ${LOCROOT}/MicrosoftFluentUI/ios/FluentUI/Resources/FluentUI-ios.xcassets ${LOCROOT}/MicrosoftFluentUI/ios
echo "=== Rebuilding resource bundle target ==="
xcodebuild ${XCODEBUILDPARAMS} -project ${PROJECT_FILE_PATH} -target "MicrosoftFluentUI-FluentUIResources-ios" -sdk ${PLATFORM_NAME} -configuration ${CONFIGURATION} ARCHS="${ARCHS}" CONFIGURATION_BUILD_DIR="${CONFIGURATION_BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" BUILT_PRODUCTS_DIR="${BUILT_PRODUCTS_DIR}" ${ACTION}',
:execution_position => :before_compile }
core_ios.preserve_paths = ["ios/FluentUI/Core/Core.resources.xcfilelist",
"scripts/removeUnusedResourcesFromAssets.swift"]
core_ios.source_files = ["ios/FluentUI/Configuration/**/*.{swift,h}",
"ios/FluentUI/Core/**/*.{swift,h}",
"ios/FluentUI/Extensions/**/*.{swift,h}"]
Expand Down Expand Up @@ -110,6 +129,7 @@ Pod::Spec.new do |s|
hud_ios.dependency 'MicrosoftFluentUI/ActivityIndicator_ios'
hud_ios.dependency 'MicrosoftFluentUI/Label_ios'
hud_ios.dependency 'MicrosoftFluentUI/TouchForwardingView_ios'
hud_ios.preserve_paths = ["ios/FluentUI/HUD/HUD.resources.xcfilelist"]
hud_ios.source_files = ["ios/FluentUI/HUD/**/*.{swift,h}"]
end

Expand All @@ -131,6 +151,7 @@ Pod::Spec.new do |s|
navigation_ios.dependency 'MicrosoftFluentUI/Avatar_ios'
navigation_ios.dependency 'MicrosoftFluentUI/Separator_ios'
navigation_ios.dependency 'MicrosoftFluentUI/TwoLineTitleView_ios'
navigation_ios.preserve_paths = ["ios/FluentUI/Navigation/Navigation.resources.xcfilelist"]
navigation_ios.source_files = ["ios/FluentUI/Navigation/**/*.{swift,h}"]
end

Expand All @@ -139,6 +160,7 @@ Pod::Spec.new do |s|
notification_ios.dependency 'MicrosoftFluentUI/Obscurable_ios'
notification_ios.dependency 'MicrosoftFluentUI/Label_ios'
notification_ios.dependency 'MicrosoftFluentUI/Separator_ios'
notification_ios.preserve_paths = ["ios/FluentUI/Notification/Notification.resources.xcfilelist"]
notification_ios.source_files = ["ios/FluentUI/Notification/**/*.{swift,h}"]
end

Expand All @@ -152,6 +174,7 @@ Pod::Spec.new do |s|
othercells_ios.platform = :ios
othercells_ios.dependency 'MicrosoftFluentUI/ActivityIndicator_ios'
othercells_ios.dependency 'MicrosoftFluentUI/TableView_ios'
othercells_ios.preserve_paths = ["ios/FluentUI/Other Cells/OtherCells.resources.xcfilelist"]
othercells_ios.source_files = ["ios/FluentUI/Other Cells/**/*.{swift,h}"]
end

Expand Down Expand Up @@ -228,13 +251,15 @@ Pod::Spec.new do |s|
tableview_ios.platform = :ios
tableview_ios.dependency 'MicrosoftFluentUI/Label_ios'
tableview_ios.dependency 'MicrosoftFluentUI/Separator_ios'
tableview_ios.preserve_paths = ["ios/FluentUI/Table View/TableView.resources.xcfilelist"]
tableview_ios.source_files = ["ios/FluentUI/Table View/**/*.{swift,h}"]
end

s.subspec 'Tooltip_ios' do |tooltip_ios|
tooltip_ios.platform = :ios
tooltip_ios.dependency 'MicrosoftFluentUI/Label_ios'
tooltip_ios.dependency 'MicrosoftFluentUI/TouchForwardingView_ios'
tooltip_ios.preserve_paths = ["ios/FluentUI/Tooltip/Tooltip.resources.xcfilelist"]
tooltip_ios.source_files = ["ios/FluentUI/Tooltip/**/*.{swift,h}"]
end

Expand All @@ -248,6 +273,7 @@ Pod::Spec.new do |s|
twoLinetitleview_ios.platform = :ios
twoLinetitleview_ios.dependency 'MicrosoftFluentUI/EasyTapButton_ios'
twoLinetitleview_ios.dependency 'MicrosoftFluentUI/Label_ios'
twoLinetitleview_ios.preserve_paths = ["ios/FluentUI/TwoLineTitleView/TwoLineTitleView.resources.xcfilelist"]
twoLinetitleview_ios.source_files = ["ios/FluentUI/TwoLineTitleView/**/*.{swift,h}"]
end

Expand Down
39 changes: 39 additions & 0 deletions ios/FluentUI/Avatar/Avatar.resources.xcfilelist
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
person_12_filled.imageset
person_12_regular.imageset
person_16_filled.imageset
person_16_regular.imageset
person_20_filled.imageset
person_20_regular.imageset
person_24_filled.imageset
person_24_regular.imageset
person_28_filled.imageset
person_28_regular.imageset
person_48_filled.imageset
person_48_regular.imageset
Presence/ic_fluent_presence_unknown_12_regular.imageset
Presence/ic_fluent_presence_unknown_16_regular.imageset
Presence/ic_fluent_presence_away_16_filled.imageset
Presence/ic_fluent_presence_busy_16_filled.imageset
Presence/ic_fluent_presence_blocked_10_regular.imageset
Presence/ic_fluent_presence_dnd_10_filled.imageset
Presence/ic_fluent_presence_available_12_filled.imageset
Presence/ic_fluent_presence_offline_10_regular.imageset
Presence/ic_fluent_presence_oof_12_regular.imageset
Presence/ic_fluent_presence_dnd_16_filled.imageset
Presence/ic_fluent_presence_oof_16_regular.imageset
Presence/ic_fluent_presence_busy_10_filled.imageset
Presence/ic_fluent_presence_away_10_filled.imageset
Presence/ic_fluent_presence_unknown_10_regular.imageset
Presence/ic_fluent_presence_available_10_filled.imageset
Presence/ic_fluent_presence_dnd_16_regular.imageset
Presence/ic_fluent_presence_blocked_16_regular.imageset
Presence/ic_fluent_presence_blocked_12_regular.imageset
Presence/ic_fluent_presence_busy_12_filled.imageset
Presence/ic_fluent_presence_away_12_filled.imageset
Presence/ic_fluent_presence_oof_10_regular.imageset
Presence/ic_fluent_presence_offline_12_regular.imageset
Presence/ic_fluent_presence_available_16_filled.imageset
Presence/ic_fluent_presence_offline_16_regular.imageset
Presence/ic_fluent_presence_dnd_12_filled.imageset
Presence/ic_fluent_presence_available_16_regular.imageset
Presence/presenceBorder.colorset
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
checkmark-24x24.imageset
checkmark-thin-20x20.imageset
1 change: 1 addition & 0 deletions ios/FluentUI/Core/Core.resources.xcfilelist
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
back-24x24.imageset
2 changes: 2 additions & 0 deletions ios/FluentUI/HUD/HUD.resources.xcfilelist
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
checkmark-36x36.imageset
dismiss-36x36.imageset
2 changes: 2 additions & 0 deletions ios/FluentUI/Navigation/Navigation.resources.xcfilelist
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
search-20x20.imageset
search-clear.imageset
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dismiss-20x20.imageset
3 changes: 3 additions & 0 deletions ios/FluentUI/Other Cells/OtherCells.resources.xcfilelist
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ic_fluent_people_24_regular.imageset
ic_fluent_person_24_regular.imageset
person_24_regular.imageset
6 changes: 6 additions & 0 deletions ios/FluentUI/Table View/TableView.resources.xcfilelist
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
checkmark-24x24.imageset
chevron-right-20x20.imageset
iOS-chevron-right-20x20.imageset
more-24x24.imageset
selection-off.imageset
selection-on.imageset
1 change: 1 addition & 0 deletions ios/FluentUI/Tooltip/Tooltip.resources.xcfilelist
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tooltip-arrow.imageset
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
chevron-down-20x20.imageset
chevron-right-20x20.imageset
114 changes: 114 additions & 0 deletions scripts/removeUnusedResourcesFromAssets.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import Foundation

if CommandLine.arguments.count != 3 {
print("usage: swift removeUnusedResourcesFromAssets.swift <xcassets_file_path> <project_root_path>")
} else {
let xcassetsPath = CommandLine.arguments[1]
let rootPath = CommandLine.arguments[2]

let usedResources = findUsedResources(in: rootPath)

removeResources(from: xcassetsPath,
notContainedIn: usedResources)
}

/// Builds a set of resource entries relative to the root of an .xcassets folder based on contents of the .xcfilelist files in the project.
/// - Parameter rootPath: Root path of the project. A search for .resources.xcfilelist files will be performed to build the resource entry list.
/// - Returns: A set containing the combination of all resource entries in the .resources.xcfilelist found in the project.
func findUsedResources(in rootPath: String) -> Set<String> {
let rootURL = URL(fileURLWithPath: rootPath)
let resourceFileSuffix = ".resources.xcfilelist"
var usedResources: Set<String> = []

#if VERBOSE_OUTPUT
print("Parsing *\(resourceFileSuffix) files in path \(rootURL)")
#endif
if let filesEnumerator = FileManager.default.enumerator(at: rootURL,
includingPropertiesForKeys: [.isRegularFileKey],
options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
for case let fileURL as URL in filesEnumerator {
do {
let fileAttributes = try fileURL.resourceValues(forKeys: [.isRegularFileKey])
let filePath = fileURL.relativePath

if fileAttributes.isRegularFile! &&
filePath.hasSuffix(resourceFileSuffix) {

#if VERBOSE_OUTPUT
print("\nUsed resources in file: \(filePath)")
#endif

do {
let resourceFileListContents = try String(contentsOf: fileURL)

for entry in resourceFileListContents.split(separator: "\n") {
let resourceFileEntry = entry.trimmingCharacters(in: .whitespacesAndNewlines)
usedResources.insert(resourceFileEntry)
#if VERBOSE_OUTPUT
print("- \(resourceFileEntry)")
#endif
}
} catch {
fatalError("Failed to read contents resource file: \(filePath) \nError: \(error)")
}
}
} catch {
fatalError("Failed to retrieve resource file: \(fileURL) \nError: \(error)")
}
}
}

return usedResources
}

/// Iterates through all folders in a given .xcassets file and removes the .colorset or .imageset folders that are not contained in the used resources set.
/// - Parameters:
/// - xcassetsPath: Root path of the .xcassets file.
/// - usedResourcesSet: Set containing the list of paths (relative to the .xcassets root) of resources that should not be removed.
func removeResources(from xcassetsPath: String, notContainedIn usedResourcesSet: Set<String>) {
let resourceExtensions = ["colorset", "imageset"]
let xcassetsURL = URL(fileURLWithPath: xcassetsPath)
let xcassetsRelativePath = xcassetsURL.relativePath

#if VERBOSE_OUTPUT
print("\n\nProcessing resources of xcassets in path: \(xcassetsPath)")
#endif

if let directoriesEnumerator = FileManager.default.enumerator(at: xcassetsURL,
includingPropertiesForKeys: [.isDirectoryKey],
options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
for case let directoryURL as URL in directoriesEnumerator {
do {
let directoryAttributes = try directoryURL.resourceValues(forKeys: [.isDirectoryKey])
let dirExtension = directoryURL.pathExtension

if directoryAttributes.isDirectory! && resourceExtensions.contains(dirExtension) {
let directoryEntry = directoryURL.relativePath.withoutPrefix(xcassetsRelativePath).withoutPrefix("/")
let shouldBeKept = usedResourcesSet.contains(directoryEntry)

if !shouldBeKept {
try FileManager.default.removeItem(at: directoryURL)
}
#if VERBOSE_OUTPUT
print(" - \(directoryEntry) (\(shouldBeKept ? "kept" : "removed"))")
#endif
}
} catch {
print(error, directoryURL)
}
}
}
}

extension String {
/// Removes a given prefix from a string.
/// - Parameter prefix: The prefix to be removed.
/// - Returns: The resulting string without the prefix.
func withoutPrefix(_ prefix: String) -> String {
guard self.hasPrefix(prefix) else {
return self
}

return String(self.dropFirst(prefix.count))
}
}

0 comments on commit 5f53813

Please sign in to comment.