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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ All notable changes to this project will be documented in this file.
### Added

- Add support for Xcode 16 synchronized folders
- Add support for Swift Packages
- Add new optional variable nammed "project_type" which accepts 2 values: "xcode" and "spm". When missing ccios use "xcode" by default.
- When "project_type" is set to "spm" ccios will not try to update a pbxproj and will only generate files.
- When "project_type" is set to "spm" multi target definition is no longer supported for generated files, as this is not supported by SPM.
- When generating files for an spm project, the target name in the header is either: the target defined in the template variables, the target defined in `.ccios.yml`, or it will guess the name of the target when your package uses the standard naming scheme of: "Sources/<target_name>/".

## [5.1.0]

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ templates_collection: ccios/templates

# Global overrides of variables [Optional]
variables:
project_type: xcode
project: Project.xcodeproj
target: SomeDefaultTarget

Expand Down Expand Up @@ -221,6 +222,8 @@ parameters:
# List of templates variables that is used to generate files in an xcode project. [Optional]
# Those variables can be overridden in config file, see section "Variable hierarchy" for more informations.
variables:
# Type of project "spm" or "xcode", will be considered as "xcode" if not specified. [Optional]
project_type: spm
# The name of the xcode project. "*.xcodeproj" will use the first it finds. [required]
project: "*.xcodeproj"
# The base path used to generate an element. This variable must be defined once here, or on each elements below.
Expand Down
14 changes: 11 additions & 3 deletions lib/ccios/file_creator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,18 @@ def templater_options(targets, project)
full_username: git_username,
date: DateTime.now.strftime("%d/%m/%Y"),
}
if targets.count == 1
defaults["project_name"] = targets[0].display_name
if project.nil?
if targets.count == 1
defaults["project_name"] = targets[0]
else
raise "A file outside an xcode project cannot require multiple targets"
end
else
defaults["project_name"] = project.project_name_from_path
if targets.count == 1
defaults["project_name"] = targets[0].display_name
else
defaults["project_name"] = project.project_name_from_path
end
end
defaults
end
Expand Down
63 changes: 48 additions & 15 deletions lib/ccios/file_template_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,34 @@ def validate(parser, project, context, template_definition, config)
base_path = merged_variables["base_path"]
raise "Missing base_path variable" if base_path.nil?

base_group = XcodeGroupRepresentation.findGroup(base_path, project)
raise "Base path \"#{base_path}\" is missing" if base_group.nil?

target_name = merged_variables["target"]
if target_name.is_a?(String) || target_name.nil?
target = parser.target_for(project, target_name)
raise "Unable to find target \"#{target_name}\"" if target.nil?
elsif target_name.is_a?(Array)
target_name.each do |target_name|
if project.nil?
if target_name.is_a?(String)
target = target_name
elsif target_name.nil?
guessed_name = guess_spm_target_name(base_path)
raise "Unable to guess the target from the base path \"#{base_path}\", please specify the target in your config file" if guessed_name.nil?
target = guessed_name
elsif target_name.is_a?(Array)
raise "A template generating files in an spm project cannot specify multiple targets"
else
raise "Invalid target in template #{@name}"
end
else
base_group = XcodeGroupRepresentation.findGroup(base_path, project)
raise "Base path \"#{base_path}\" is missing" if base_group.nil?

if target_name.is_a?(String) || target_name.nil?
target = parser.target_for(project, target_name)
raise "Unable to find target \"#{target_name}\"" if target.nil?
elsif target_name.is_a?(Array)
target_name.each do |target_name|
target = parser.target_for(project, target_name)
raise "Unable to find target \"#{target_name}\"" if target.nil?
end
else
raise "Invalid target in template #{@name}"
end
else
raise "Invalid target in template #{@name}"
end

end
Expand All @@ -66,10 +80,19 @@ def generate(parser, project, context, template_definition, config)
target_name = merged_variables["target"]

targets = []
if target_name.is_a?(String) || target_name.nil?
targets = [parser.target_for(project, target_name)]
elsif target_name.is_a?(Array)
targets = target_name.map { |name| parser.target_for(project, name) }
if project.nil?
if target_name.is_a?(String)
targets = [target_name]
elsif target_name.nil?
guessed_name = guess_spm_target_name(base_path)
targets = [guessed_name]
end
else
if target_name.is_a?(String) || target_name.nil?
targets = [parser.target_for(project, target_name)]
elsif target_name.is_a?(Array)
targets = target_name.map { |name| parser.target_for(project, name) }
end
end

FileCreator.new.create_file_using_template_path(
Expand All @@ -81,4 +104,14 @@ def generate(parser, project, context, template_definition, config)
context
)
end
end

private def guess_spm_target_name(path)
parts = path.split(File::SEPARATOR)
sources_index = parts.index("Sources")
if sources_index && sources_index + 1 < parts.length
return parts[sources_index + 1]
else
return nil
end
end
end
2 changes: 1 addition & 1 deletion lib/ccios/group_template_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ def generate(parser, project, context, template_definition, config)
file_creator = FileCreator.new
file_creator.create_empty_directory_for_group(group)
end
end
end
2 changes: 1 addition & 1 deletion lib/ccios/pbxproj_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ def target_for(project, target_name)
project.targets.find { |t| t.name == target_name }
end
end
end
end
26 changes: 20 additions & 6 deletions lib/ccios/template_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,17 @@ def validate(parser, options, config)
raise "Error: invalid template name" unless @name.is_a? String

merged_variables = config.variables_for_template(self)
project_path = merged_variables["project"]

project = parser.project_for(project_path)
raise "Error: Unable to find project \"#{project_path}\"" if project.nil?
project_type = merged_variables["project_type"] || "xcode"
case project_type
when "xcode"
project_path = merged_variables["project"]
project = parser.project_for(project_path)
raise "Error: Unable to find project \"#{project_path}\"" if project.nil?
when "spm"
project = nil
else
raise "Invalid project_type given \"#{project_type}\""
end

@template_file_source.each do |file_template_name, path|
raise "Missing template source file for \"#{file_template_name}\"" unless File.exist?(self.template_source_file(file_template_name))
Expand Down Expand Up @@ -93,9 +100,16 @@ def generate(parser, options, config)
options = agrument_transformed_options(options)

merged_variables = config.variables_for_template(self)
project_path = merged_variables["project"]

project = parser.project_for project_path
project_type = merged_variables["project_type"] || "xcode"
case project_type
when "xcode"
project_path = merged_variables["project"]
project = parser.project_for project_path
when "spm"
project = nil

end

@generated_elements.each do |element|
element.generate(parser, project, options, self, config)
Expand Down
30 changes: 23 additions & 7 deletions lib/ccios/xcode_group_representation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

# This object handles Xcode groups, folder reference and synchronized folder reference
class XcodeGroupRepresentation

def self.findGroup(path, project)
intermediates_groups = path.split("/")

if project.nil?
return self.new nil, nil, intermediates_groups
end

deepest_group = project.main_group
additional_path = []

Expand All @@ -22,10 +26,15 @@ def self.findGroup(path, project)
end

def initialize(project, xcode_group, additional_path = [])
throw "Unsupported group type" unless xcode_group.is_a?(Xcodeproj::Project::Object::PBXFileSystemSynchronizedRootGroup) || xcode_group.is_a?(Xcodeproj::Project::Object::PBXGroup)
if !additional_path.empty? && !xcode_group.is_a?(Xcodeproj::Project::Object::PBXFileSystemSynchronizedRootGroup)
throw "additional_path can only be specified for a synchronized file system group"
if project.nil?
throw "Unexpected xcode_group when project is nil, we should be in an spm context" unless xcode_group.nil?
else
throw "Unsupported group type" unless xcode_group.is_a?(Xcodeproj::Project::Object::PBXFileSystemSynchronizedRootGroup) || xcode_group.is_a?(Xcodeproj::Project::Object::PBXGroup)
if !additional_path.empty? && !xcode_group.is_a?(Xcodeproj::Project::Object::PBXFileSystemSynchronizedRootGroup)
throw "additional_path can only be specified for a synchronized file system group"
end
end
# This represent the xcode project if present, if nil we should be generating files in an Swift package
@project = project
# This represents the deepest group or folder reference in the project
@xcode_deepest_group = xcode_group
Expand All @@ -34,11 +43,18 @@ def initialize(project, xcode_group, additional_path = [])
end

def real_path
Xcodeproj::Project::Object::GroupableHelper.real_path(@xcode_deepest_group) + @additional_path.join("/")
if @xcode_deepest_group.nil?
@additional_path.join("/")
else
Xcodeproj::Project::Object::GroupableHelper.real_path(@xcode_deepest_group) + @additional_path.join("/")
end
end


def register_file_to_targets(file_path, targets)
if @xcode_deepest_group.nil?
return
end
if @xcode_deepest_group.is_a?(Xcodeproj::Project::Object::PBXGroup)
file_ref = @xcode_deepest_group.new_reference(file_path)
targets.each do |target|
Expand All @@ -61,7 +77,7 @@ def create_groups_if_needed_for_intermediate_groups(intermediates_groups)
new_additional_path = @additional_path

intermediates_groups.each do |group_name|
if new_deepest_group.is_a?(Xcodeproj::Project::Object::PBXFileSystemSynchronizedRootGroup)
if new_deepest_group.nil? || new_deepest_group.is_a?(Xcodeproj::Project::Object::PBXFileSystemSynchronizedRootGroup)
new_additional_path.append(group_name)
elsif new_deepest_group.is_a?(Xcodeproj::Project::Object::PBXGroup)
existing_child = new_deepest_group.find_subpath(group_name)
Expand All @@ -88,4 +104,4 @@ def pf_new_group(associate_path_to_group:, name:, path:)
associate_path_to_group ? path : nil
)
end
end
end
8 changes: 8 additions & 0 deletions test/swift_package/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
30 changes: 30 additions & 0 deletions test/swift_package/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// swift-tools-version: 6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "swift_package",
platforms: [
.iOS(.v18)
],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "swift_package",
targets: ["App"]),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "App",
dependencies: ["Core", "Data"]
),
.target(name: "Core"),
.target(
name: "Data",
dependencies: ["Core"]
)
]
)
2 changes: 2 additions & 0 deletions test/swift_package/Sources/App/App.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book
2 changes: 2 additions & 0 deletions test/swift_package/Sources/App/Coordinator/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book
2 changes: 2 additions & 0 deletions test/swift_package/Sources/App/Screen/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book
2 changes: 2 additions & 0 deletions test/swift_package/Sources/Core/Core.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book
2 changes: 2 additions & 0 deletions test/swift_package/Sources/Core/Interactor/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book
2 changes: 2 additions & 0 deletions test/swift_package/Sources/Core/Repository/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book
2 changes: 2 additions & 0 deletions test/swift_package/Sources/Data/Data.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book
Loading