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