SPM: τι είναι, Swift Package Manager και Package.swift

Συγγραφέας: IT Sectr Δημοσιεύτηκε: 2026-02-13 Χρόνος ανάγνωσης: 11 λεπ

SPM (Swift Package Manager) — ο ενσωματωμένος διαχειριστής πακέτων του οικοσυστήματος Swift, που αναπτύχθηκε από την Apple για την αυτοματοποίηση της σύνδεσης, δημιουργίας και ενημέρωσης βιβλιοθηκών τρίτων. Το SPM αποτελεί μέρος του μεταγλωττιστή Swift από την έκδοση 3.0 (2016) και δεν απαιτεί ξεχωριστή εγκατάσταση. Σε αντίθεση με τα CocoaPods και Carthage, το SPM είναι ενσωματωμένο απευθείας με τον μεταγλωττιστή και το Xcode, καθιστώντας το το τυπικό εργαλείο διαχείρισης εξαρτήσεων σε σύγχρονα έργα Swift. Στο άρθρο θα αναλύσουμε τη δομή του Package.swift, τις εντολές SPM, τη σύνταξη δικών σας πακέτων και τη μετεγκατάσταση από εναλλακτικούς διαχειριστές.

Κύρια σημεία

  • SPM (Swift Package Manager) — ενσωματωμένος διαχειριστής πακέτων στον μεταγλωττιστή Swift, που δεν απαιτεί ξεχωριστή εγκατάσταση; λειτουργεί σε iOS, macOS, Linux και πλατφόρμες διακομιστή.
  • Package.swift — αρχείο manifest που περιγράφει το όνομα πακέτου, πλατφόρμες, εξαρτήσεις και αρθρώματα-στόχους (targets) σε δηλωτική μορφή.
  • Το SPM επιλύει εξαρτήσεις βάσει σημασιολογικής έκδοσης (SemVer), αποθηκεύει προσωρινά τον πηγαίο κώδικα και δημιουργεί πακέτα παράλληλα για επιτάχυνση.
  • Εντολές: swift package init (δημιουργία πακέτου), swift package update (ενημέρωση εξαρτήσεων), swift build (δημιουργία), swift test (εκτέλεση δοκιμών).
  • Η μετεγκατάσταση από CocoaPods/Carthage σε SPM γίνεται μέσω Xcode: File → Add Package Dependency, μετά την οποία τα podfile και Cartfile διαγράφονται.

Τι είναι το SPM;

SPM (Swift Package Manager) — ο επίσημος διαχειριστής πακέτων για τη γλώσσα Swift, ενσωματωμένος στον μεταγλωττιστή swiftc και το περιβάλλον ανάπτυξης Xcode. Επιτρέπει στους προγραμματιστές να συνδέουν βιβλιοθήκες τρίτων, να διαχειρίζονται τις εκδόσεις τους και να δημοσιεύουν δικά τους πακέτα. Το SPM εμφανίστηκε για πρώτη φορά στο Swift 3.0 (Σεπτέμβριος 2016) ως εργαλείο γραμμής εντολών, και από το Xcode 11 (2019) απέκτησε πλήρη ενσωμάτωση με τη γραφική διεπαφή — τώρα οι εξαρτήσεις προστίθενται μέσω του μενού File → Add Packages.

Το SPM κατεβάζει αυτόματα τον πηγαίο κώδικα των εξαρτήσεων από αποθετήρια Git, τα δημιουργεί παράλληλα με το κύριο έργο και αποθηκεύει προσωρινά τα αποτελέσματα για ταχύτερη εκ νέου δημιουργία. Σε αντίθεση με τα CocoaPods, το SPM δεν δημιουργεί ξεχωριστό χώρο εργασίας (xcworkspace) — οι εξαρτήσεις γίνονται μέρος του κύριου έργου Xcode. Σύμφωνα με την έρευνα Swift.org Developer Survey (2024), το SPM χρησιμοποιείται από 67% των προγραμματιστών iOS, καθιστώντας το το πιο δημοφιλές εργαλείο διαχείρισης εξαρτήσεων στο οικοσύστημα Swift.

Το SPM υποστηρίζει τρεις πλατφόρμες: Apple (iOS, macOS, tvOS, watchOS, visionOS), Linux (Ubuntu, CentOS, Amazon Linux) και Swift διακομιστή (Vapor, Kitura). Στο Linux, το SPM λειτουργεί πλήρως μέσω γραμμής εντολών χωρίς Xcode.

Πώς λειτουργεί το Swift Package Manager

Το SPM είναι χτισμένο γύρω από τρεις βασικές έννοιες: πακέτα (packages), προϊόντα (products) και στόχους (targets). Ένα πακέτο είναι ένα αποθετήριο Git με manifest Package.swift. Ένα προϊόν είναι το αποτέλεσμα δημιουργίας (βιβλιοθήκη ή εκτελέσιμο αρχείο). Ένας στόχος είναι μια ενότητα μέσα στο πακέτο που μεταγλωττίζεται σε μονάδα δημιουργίας.

Όταν ένας προγραμματιστής προσθέτει μια εξάρτηση στο Package.swift, το SPM εκτελεί τα ακόλουθα βήματα:

  1. Κλωνοποίηση — Το SPM κατεβάζει το αποθετήριο Git της εξάρτησης από τη δεδομένη διεύθυνση URL.
  2. Επίλυση εκδόσεων — αναλύει ετικέτες SemVer (π.χ. 2.1.3) και επιλέγει την κατάλληλη έκδοση εντός του καθορισμένου εύρους.
  3. Μεταβατική επίλυση — ελέγχει τις εξαρτήσεις των εξαρτήσεων και χτίζει ένα γράφημα χωρίς συγκρούσεις εκδόσεων.
  4. Προσωρινή αποθήκευση — αποθηκεύει τον ληφθέντα πηγαίο κώδικα στο ~Library/Caches/org.swift.swiftpm/.
  5. Μεταγλώττιση — δημιουργεί όλους τους στόχους του πακέτου με τις σημαίες του κύριου έργου.

Το αρχείο Package.resolved καθορίζει τις ακριβείς εκδόσεις όλων των εξαρτήσεων, ώστε η ομάδα προγραμματιστών να εργάζεται με το ίδιο σύνολο βιβλιοθηκών. Αυτό το αρχείο πρέπει να προστεθεί στο σύστημα ελέγχου εκδόσεων (git).

Το βασικό πλεονέκτημα του SPM έναντι των εναλλακτικών — απουσία κεντρικού μητρώου. Τα πακέτα μπορούν να βρίσκονται σε οποιοδήποτε δημόσιο αποθετήριο Git: GitHub, GitLab, Bitbucket, καθώς και σε ιδιωτικούς διακομιστές Git της εταιρείας. Από την έκδοση Swift 5.2, το SPM υποστηρίζει δυαδικές εξαρτήσεις (binary targets) — κλειστές βιβλιοθήκες που διανέμονται μέσω XCFramework χωρίς παροχή πηγαίου κώδικα.

Package.swift — το manifest του έργου

Package.swift — είναι ένα αρχείο Swift που περιγράφει τη δομή του πακέτου και τις εξαρτήσεις του. Το αρχείο γράφεται στην ίδια τη Swift (όχι JSON, όχι YAML), γεγονός που επιτρέπει τη χρήση υπό όρων λογικής, υπολογιζόμενων σταθερών και συναρτήσεων εντός του manifest.

Βασική δομή του Package.swift:

swift
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyLibrary",
    platforms: [
        .iOS(.v16),
        .macOS(.v13)
    ],
    products: [
        .library(
            name: "MyLibrary",
            targets: ["MyLibrary"]
        ),
    ],
    dependencies: [
        .package(url: "https://github.com/Alamofire/Alamofire.git",
                 from: "5.9.0"),
        .package(url: "https://github.com/onevcat/Kingfisher.git",
                 from: "7.12.0"),
    ],
    targets: [
        .target(
            name: "MyLibrary",
            dependencies: [
                "Alamofire",
                "Kingfisher"
            ]
        ),
        .testTarget(
            name: "MyLibraryTests",
            dependencies: ["MyLibrary"]
        ),
    ]
)

Ας αναλύσουμε τα βασικά στοιχεία:

  • // swift-tools-version: 5.9 — οδηγία που υποδεικνύει την έκδοση SPM; η διαθέσιμη σύνταξη του manifest εξαρτάται από αυτήν.
  • name — το όνομα του πακέτου, που εμφανίζεται στο Xcode και χρησιμοποιείται σε αναφορές εξαρτήσεων.
  • platforms — ελάχιστες εκδόσεις πλατφορμών; το SPM δεν θα επιτρέψει τη δημιουργία του πακέτου σε παλαιότερη έκδοση OS.
  • products — τι "εξάγει" το πακέτο: βιβλιοθήκη (.library) ή εκτελέσιμο αρχείο (.executable).
  • dependencies — λίστα εξωτερικών πακέτων με URL και έκδοση; υποστηρίζονται from:, exact:, branch:, revision:.
  • targets — στόχοι δημιουργίας; κάθε στόχος περιέχει λίστα εξαρτήσεων, πόρων και αρχείων swift από τον αντίστοιχο κατάλογο (Sources/TargetName/).

Παράδειγμα καθορισμού ακριβούς έκδοσης, κλάδου και commit:

swift
dependencies: [
    .package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git",
             exact: "1.17.3"),
    .package(url: "https://github.com/pointfreeco/swift-composable-architecture.git",
             branch: "main"),
    .package(url: "https://github.com/apple/swift-log.git",
             revision: "e5c6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4"),
]

Από την έκδοση Swift 5.9, προστέθηκε υποστήριξη για static/framework και linkerSettings στο Package.swift, επιτρέποντας ακριβέστερη ρύθμιση σύνδεσης για στατικές και δυναμικές βιβλιοθήκες.

Βασικές εντολές SPM

Το Swift Package Manager παρέχει ένα σύνολο εντολών για εργασία μέσω τερματικού. Οι εντολές εκτελούνται από τον ριζικό κατάλογο του πακέτου (όπου βρίσκεται το Package.swift).

bash
# Δημιουργία νέου πακέτου με βιβλιοθήκη
swift package init --type library

# Δημιουργία εκτελέσιμου πακέτου (εφαρμογή κονσόλας)
swift package init --type executable

# Δημιουργία έργου
swift build

# Δημιουργία σε διαμόρφωση release
swift build -c release

# Εκτέλεση δοκιμών
swift test

# Εκτέλεση συγκεκριμένης δοκιμής
swift test --filter "MyLibraryTests/testExample"

# Λήψη και επίλυση εξαρτήσεων
swift package resolve

# Ενημέρωση εξαρτήσεων στις πιο πρόσφατες διαθέσιμες εκδόσεις
swift package update

# Εμφάνιση γραφήματος εξαρτήσεων
swift package show-dependencies

# Εκκαθάριση προσωρινής μνήμης δημιουργίας
swift package clean

# Δημιουργία έργου Xcode (πριν από Xcode 11)
swift package generate-xcodeproj

Κατά την εργασία εντός Xcode, οι περισσότερες από αυτές τις εντολές εκτελούνται αυτόματα: οι εξαρτήσεις επιλύονται κατά το άνοιγμα του έργου, η δημιουργία ξεκινά με ⌘B, οι δοκιμές με ⌘U. Ωστόσο, η γνώση των εντολών τερματικού είναι απαραίτητη για αγωγούς CI/CD (GitHub Actions, GitLab CI, Jenkins), όπου το Xcode δεν είναι διαθέσιμο.

Η εντολή swift package resolve δημιουργεί ή ενημερώνει το αρχείο Package.resolved. Αυτό το αρχείο καθορίζει τις ακριβείς εκδόσεις όλων των εξαρτήσεων, συμπεριλαμβανομένων των μεταβατικών, και πρέπει να προστεθεί στο git. Συνιστάται η εκτέλεση του swift package update πριν από κάθε νέο κλάδο λειτουργίας, για να εργάζεστε με τις τρέχουσες εκδόσεις βιβλιοθηκών.

Δημιουργία δικού σας πακέτου

Η δημιουργία δικού σας πακέτου SPM είναι χρήσιμη για την ενθυλάκωση επιχειρηματικής λογικής σε πολυαρθρωτικά έργα και για τη δημοσίευση βιβλιοθηκών ανοιχτού κώδικα. Ας δούμε τη διαδικασία βήμα προς βήμα.

Βήμα 1: Αρχικοποίηση

bash
mkdir MyNetworkKit
cd MyNetworkKit
swift package init --type library

Βήμα 2: Δομή καταλόγου

Η εντολή swift package init δημιουργεί την ακόλουθη δομή:

text
MyNetworkKit/
├── Package.swift
├── README.md
├── Sources/
│   └── MyNetworkKit/
│       └── MyNetworkKit.swift
└── Tests/
    └── MyNetworkKitTests/
        └── MyNetworkKitTests.swift

Το SPM σαρώνει αυτόματα τους καταλόγους Sources/ και Tests/: κάθε υποκατάλογος εντός Sources αντιστοιχεί σε έναν στόχο (target).

Βήμα 3: Επεξεργασία Package.swift

Ας προσθέσουμε εξαρτήσεις και να ρυθμίσουμε τις πλατφόρμες-στόχους:

swift
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyNetworkKit",
    platforms: [
        .iOS(.v15),
        .macOS(.v12)
    ],
    products: [
        .library(
            name: "MyNetworkKit",
            targets: ["MyNetworkKit"]
        ),
    ],
    dependencies: [
        .package(url: "https://github.com/Alamofire/Alamofire.git",
                 from: "5.9.0"),
    ],
    targets: [
        .target(
            name: "MyNetworkKit",
            dependencies: ["Alamofire"]
        ),
        .testTarget(
            name: "MyNetworkKitTests",
            dependencies: ["MyNetworkKit"]
        ),
    ]
)

Βήμα 4: Σύνταξη κώδικα

swift
// Sources/MyNetworkKit/MyNetworkKit.swift
import Foundation
import Alamofire

public struct NetworkClient {
    private let session: Session

    public init() {
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = 30
        self.session = Session(configuration: configuration)
    }

    public func fetchData(from url: String) async throws -> Data {
        let response = try await session.request(url).serializingData().value
        return response
    }
}

Βήμα 5: Δημοσίευση

Σπρώξτε το πακέτο στο αποθετήριο Git και δημιουργήστε μια ετικέτα SemVer:

bash
git init
git add .
git commit -m "Initial commit: MyNetworkKit"
git remote add origin https://github.com/username/MyNetworkKit.git
git push -u origin main
git tag 1.0.0
git push --tags

Μετά από αυτό, οποιοσδήποτε προγραμματιστής μπορεί να συνδέσει το πακέτο σας μέσω .package(url: "https://github.com/username/MyNetworkKit.git", from: "1.0.0").

Παραδείγματα χρήσης SPM

Παράδειγμα 1: Σύνδεση Alamofire για αιτήματα δικτύου

Το Alamofire — ο πιο δημοφιλής πελάτης HTTP για Swift. Ας το προσθέσουμε μέσω SPM και να εκτελέσουμε ένα αίτημα GET.

swift
import Alamofire

func fetchUsers() {
    AF.request("https://jsonplaceholder.typicode.com/users")
        .validate()
        .responseDecodable(of: [User].self) { response in
            switch response.result {
            case .success(let users):
                print("Λήφθηκαν \(users.count) χρήστες")
            case .failure(let error):
                print("Σφάλμα: \(error.localizedDescription)")
            }
        }
}

Παράδειγμα 2: Swinject — έγχυση εξαρτήσεων

Η βιβλιοθήκη Swinject παρέχει ένα κοντέινερ DI για Swift. Συνδέεται μέσω .package(url: "https://github.com/Swinject/Swinject.git", from: "2.8.0").

swift
import Swinject

let container = Container()
container.register(NetworkServiceProtocol.self) { _ in NetworkService() }
container.register(DataRepositoryProtocol.self) { r in
    DataRepository(networkService: r.resolve(NetworkServiceProtocol.self)!)
}

let repository = container.resolve(DataRepositoryProtocol.self)
repository?.loadData()

Παράδειγμα 3: Swift-log για δομημένη καταγραφή

Το πακέτο swift-log από την Apple — ένα ενοποιημένο API καταγραφής που υποστηρίζει πολλαπλά backend (OSLog, κονσόλα, αρχεία).

swift
import Logging

var logger = Logger(label: "com.myapp.network")
logger.logLevel = .debug

logger.info("Το αίτημα δικτύου ξεκίνησε", metadata: [
    "url": "\(requestURL)",
    "method": "GET"
])

logger.warning("Ο χρόνος απόκρισης υπερέβη τα 2 δευτερόλεπτα")
logger.error("Σφάλμα σύνδεσης: δεν υπάρχει διαδίκτυο")

Αυτά τα τρία παραδείγματα καλύπτουν τυπικά σενάρια χρήσης SPM: πελάτες HTTP, κοντέινερ DI και υποδομή συστήματος. Η επιλογή βιβλιοθηκών δεν είναι τυχαία — τα Alamofire, Swinject και swift-log βρίσκονται στην κορυφαία 20άδα των πιο δημοφιλών πακέτων Swift στο GitHub.

Μετεγκατάσταση από CocoaPods και Carthage

Εάν το έργο χρησιμοποιεί CocoaPods ή Carthage, η μετεγκατάσταση σε SPM γίνεται σε λίγα βήματα. Η διαδικασία είναι ασφαλής: οι εξαρτήσεις SPM μπορούν να συνυπάρχουν με CocoaPods και Carthage στο ίδιο έργο, επιτρέποντας σταδιακή μετεγκατάσταση.

CocoaPods → SPM

  1. Στο Xcode: File → Add Package Dependency, εισαγάγετε τη διεύθυνση URL του πακέτου.
  2. Επιλέξτε έκδοση και προσθέστε το πακέτο στα απαιτούμενα targets.
  3. Αφού προσθέσετε όλες τις εξαρτήσεις μέσω SPM, διαγράψτε τις γραμμές από το Podfile.
  4. Διαγράψτε το .xcworkspace, ανοίξτε το .xcodeproj και εκτελέστε Clean Build Folder.

Carthage → SPM

  1. Προσθέστε πακέτα μέσω Xcode File → Add Package Dependency.
  2. Διαγράψτε τις εξαρτήσεις από το Cartfile.
  3. Διαγράψτε τα σενάρια δημιουργίας Carthage από το Build Phases.
  4. Εκκαθαρίστε την προσωρινή μνήμη: rm -rf Carthage/ στο τερματικό.

Από το 2025, το SPM υποστηρίζει τη συντριπτική πλειοψηφία των δημοφιλών βιβλιοθηκών Swift. Εξαιρέσεις αποτελούν ορισμένα πλαίσια ObjC χωρίς χάρτες αρθρωμάτων (modulemap). Εάν μια βιβλιοθήκη δεν υποστηρίζει ακόμη SPM — ελέγξτε την ενότητα Installation στο README της; οι περισσότεροι συγγραφείς έχουν ήδη προσθέσει υποστήριξη SPM στις τελευταίες εκδόσεις.

Συχνές ερωτήσεις

Σε τι διαφέρει το SPM από τα CocoaPods και Carthage;

Το SPM είναι ενσωματωμένο στον μεταγλωττιστή Swift και το Xcode, δεν απαιτεί εγκατάσταση μέσω gem ή Homebrew. Τα CocoaPods χρησιμοποιούν κεντρικό μητρώο Specs και δημιουργούν ξεχωριστό χώρο εργασίας. Το Carthage λειτουργεί μέσω πλαισίων χωρίς ενσωμάτωση στο έργο. Το SPM είναι ο μόνος διαχειριστής που ενσωματώνεται σε επίπεδο μεταγλωττιστή: οι εξαρτήσεις επιλύονται, αποθηκεύονται προσωρινά και δημιουργούνται παράλληλα με τον κύριο κώδικα.

Μπορεί το SPM να χρησιμοποιηθεί για έργα Objective-C;

Ναι, το SPM υποστηρίζει μικτά έργα Swift + Objective-C. Τα αρχεία ObjC εντός του πακέτου SPM εισέρχονται αυτόματα στο Umbrella Header υπό την προϋπόθεση σωστού modulemap. Ωστόσο, το SPM δεν υποστηρίζει στατικές βιβλιοθήκες ObjC που δεν διαθέτουν χάρτη αρθρωμάτων. Συνιστάται η σύνδεση βιβλιοθηκών ObjC μέσω SPM μόνο εάν παρέχουν modulemap ή είναι γραμμένες σε καθαρή C.

Πώς επιλύει το SPM συγκρούσεις εκδόσεων;

Το SPM χρησιμοποιεί σημασιολογική έκδοση (SemVer). Εάν το πακέτο A απαιτεί Alamofire 5.8+, και το πακέτο B απαιτεί Alamofire 5.9+, το SPM θα επιλέξει την έκδοση 5.9.x που ικανοποιεί και τα δύο. Εάν η σύγκρουση είναι ανεπίλυτη (το ένα πακέτο απαιτεί 5.x, το άλλο 6.x), το SPM θα αναφέρει σφάλμα. Σε αυτήν την περίπτωση, πρέπει να ενημερώσετε ένα από τα πακέτα ή να αλλάξετε την εξάρτηση σε μια έκδοση συμβατή και με τις δύο απαιτήσεις.

Πού αποθηκεύονται τα ληφθέντα πακέτα SPM;

Σε macOS: ~Library/Caches/org.swift.swiftpm/ και ~/Library/Developer/Xcode/DerivedData/. Σε Linux: ~cache/swiftpm/. Κατά τη δημιουργία, το SPM αποθηκεύει προσωρινά τον πηγαίο κώδικα και τα μεταγλωττισμένα αρχεία αντικειμένων. Για πλήρη εκκαθάριση της προσωρινής μνήμης, εκτελέστε swift package reset — αυτή η εντολή διαγράφει την προσωρινή μνήμη εξαρτήσεων και το DerivedData για το τρέχον έργο.

Υποστηρίζει το SPM κλειστές (ιδιόκτητες) βιβλιοθήκες;

Ναι, από το Swift 5.2, το SPM υποστηρίζει δυαδικούς στόχους (binary targets). Η κλειστή βιβλιοθήκη παρέχεται ως XCFramework, και στο Package.swift καθορίζεται η διαδρομή προς το .xcframework. Ο πηγαίος κώδικας δεν αποκαλύπτεται. Ο δυαδικός στόχος καθορίζεται μέσω .binaryTarget(name: "PrivateSDK", path: "Sources/PrivateSDK.xcframework"). Αυτό επιτρέπει τη σύνδεση εμπορικών SDK χωρίς παραβίαση συμφωνιών άδειας χρήσης.

Σύνοψη

  • SPM (Swift Package Manager) — ενσωματωμένος διαχειριστής πακέτων Swift που δεν απαιτεί ξεχωριστή εγκατάσταση και είναι ενσωματωμένος με Xcode και μεταγλωττιστή.
  • Package.swift — δηλωτικό manifest στη γλώσσα Swift που περιγράφει το όνομα πακέτου, πλατφόρμες, εξαρτήσεις, προϊόντα και στόχους δημιουργίας.
  • Το SPM χρησιμοποιεί αποθετήρια Git ως πηγή πακέτων και επιλύει εκδόσεις σύμφωνα με SemVer, αποθηκεύοντας προσωρινά τον πηγαίο κώδικα για ταχύτερη εκ νέου δημιουργία.
  • Βασικές εντολές: swift package init (δημιουργία πακέτου), swift build (δημιουργία), swift test (δοκιμή), swift package update (ενημέρωση εξαρτήσεων).
  • Το δικό σας πακέτο δημιουργείται μέσω swift package init, δημοσιεύεται στο Git και είναι διαθέσιμο σε άλλα έργα μέσω URL με ετικέτα SemVer.
  • Η μετεγκατάσταση από CocoaPods/Carthage σε SPM είναι ασφαλής: οι εξαρτήσεις μπορούν να συνυπάρχουν, η μετεγκατάσταση γίνεται μέσω File → Add Package Dependency στο Xcode.
  • SPM — το πρότυπο διαχείρισης εξαρτήσεων στο οικοσύστημα Swift, που χρησιμοποιείται από το 67% των προγραμματιστών iOS (Swift.org Developer Survey, 2024).

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης