1// Copyright 2015-present 650 Industries. All rights reserved.
2
3#import "EXBuildConstants.h"
4#import "EXVersions.h"
5#import "EXKernelUtil.h"
6
7@import EXManifests;
8
9@interface EXVersions ()
10
11- (void)_loadVersions;
12
13@end
14
15@implementation NSString (EXVersions)
16
17- (NSArray <NSNumber *>*)versionComponents
18{
19  NSArray *stringComponents = [self componentsSeparatedByString:@"."];
20  NSMutableArray <NSNumber *>* components = [NSMutableArray arrayWithCapacity:stringComponents.count];
21  for (NSString *component in stringComponents) {
22    [components addObject:@([component integerValue])];
23  }
24  return components;
25}
26
27@end
28
29@implementation EXVersions
30
31+ (nonnull instancetype)sharedInstance
32{
33  static EXVersions *theVersions;
34  static dispatch_once_t once;
35  dispatch_once(&once, ^{
36    if (!theVersions) {
37      theVersions = [[EXVersions alloc] init];
38    }
39  });
40  return theVersions;
41}
42
43- (instancetype)init
44{
45  if (self = [super init]) {
46    [self _loadVersions];
47  }
48  return self;
49}
50
51+ (NSString *)versionedString:(NSString *)string withPrefix:(NSString *)symbolPrefix
52{
53  if (!string || !symbolPrefix) {
54    return nil;
55  }
56  return [NSString stringWithFormat:@"%@%@", symbolPrefix, string];
57}
58
59- (NSString *)symbolPrefixForSdkVersion:(NSString *)version isKernel:(BOOL)isKernel
60{
61#ifdef INCLUDES_VERSIONED_CODE
62  NSDictionary *detachedVersions = _versions[@"detachedNativeVersions"];
63  if (detachedVersions) {
64    if (!isKernel && detachedVersions[@"shell"]) {
65      // we are in a detached shell scenario, so we always want to leave the shell unprefixed
66      return @"";
67    }
68    if (isKernel && detachedVersions[@"shell"] && detachedVersions[@"kernel"]) {
69      if ([detachedVersions[@"shell"] isEqualToString:detachedVersions[@"kernel"]]) {
70        // kernel version matches shell version, so run them both unprefixed
71        return @"";
72      } else {
73        // kernel needs to run on prefixed code for the given kernel version, continue
74        version = detachedVersions[@"kernel"];
75      }
76    }
77  }
78  if (version && version.length && ![version isEqualToString:@"UNVERSIONED"]) {
79    return [[@"ABI" stringByAppendingString:version] stringByReplacingOccurrencesOfString:@"." withString:@"_"];
80  }
81#endif
82  return @"";
83}
84
85- (NSString *)availableSdkVersionForManifest:(EXManifestsManifest * _Nullable)manifest
86{
87  return [self _versionForManifest:manifest];
88}
89
90#pragma mark - Internal
91
92- (NSString *)_versionForManifest:(EXManifestsManifest * _Nullable)manifest
93{
94  if (manifest && manifest.expoGoSDKVersion) {
95    NSString *sdkVersion = manifest.expoGoSDKVersion;
96    NSArray *sdkVersions = _versions[@"sdkVersions"];
97    if (sdkVersion && sdkVersions) {
98      for (NSString *availableVersion in sdkVersions) {
99        if ([sdkVersion isEqualToString:availableVersion]) {
100          if (_temporarySdkVersion) {
101            NSArray <NSNumber *>* versionComponents = [availableVersion versionComponents];
102            BOOL isTemporary = (versionComponents.count > 1 && versionComponents[1].integerValue != 0);
103            if (isTemporary && [availableVersion isEqualToString:_temporarySdkVersion]) {
104              // no prefix if we're just using the current version
105              break;
106            }
107          }
108          return availableVersion;
109        }
110      }
111    }
112  }
113  return @"";
114}
115
116- (void)_loadVersions
117{
118  NSString *versionsPath = [[NSBundle mainBundle] pathForResource:@"EXSDKVersions" ofType:@"plist"];
119  NSMutableDictionary *mutableVersions = (versionsPath) ? [NSMutableDictionary dictionaryWithContentsOfFile:versionsPath] : [NSMutableDictionary dictionary];
120  if (mutableVersions[@"detachedNativeVersions"]) {
121    NSDictionary *detachedNativeVersions = mutableVersions[@"detachedNativeVersions"];
122    _temporarySdkVersion = detachedNativeVersions[@"shell"];
123  } else {
124    _temporarySdkVersion = [EXBuildConstants sharedInstance].temporarySdkVersion;
125  }
126  if (!_temporarySdkVersion) {
127    // no temporary sdk version specified in any way, fall back to using the highest version
128    NSArray *sdkVersions = mutableVersions[@"sdkVersions"];
129    NSUInteger highestVersion = 0;
130    if (sdkVersions) {
131      for (NSString *availableVersion in sdkVersions) {
132        NSArray <NSNumber *>* versionComponents = [availableVersion versionComponents];
133        if (versionComponents.count > 1 && versionComponents[0].integerValue > highestVersion) {
134          highestVersion = versionComponents[0].integerValue;
135          _temporarySdkVersion = availableVersion;
136        }
137      }
138    }
139  }
140
141  NSAssert((mutableVersions[@"sdkVersions"] != nil), @"No SDK versions are specified for the Expo kernel. Is the project missing EXSDKVersions.plist?");
142
143  _versions = mutableVersions;
144}
145
146- (BOOL)supportsVersion:(NSString *)sdkVersion {
147#ifdef INCLUDES_VERSIONED_CODE
148  return [_versions[@"sdkVersions"] containsObject:(NSString *) sdkVersion];
149#else
150  return YES;
151#endif
152}
153
154@end
155