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